Компактный make для STM32 с USB

Обзор

Дата появления микроконтроллеров STM32, судя по datasheet, 2007 год, и с тех пор они признаны как очень успешным и популярный чип. Поэтому для них уже написано множество готовых примеров с поддержкой почти всех технологий как самим вендором так и комьюнити. Это и всевозможные USB устройства включая флешку и звуковую карту, и целочисленные варианты кодеков (WMA, Speex), и даже готовые сборки под ethernet с lwip стеком так же в наличии. В это время комьюнити пытается догнать вендора и написало библиотеку libopencm3, уже в отличии от оригинала, основанную на make, а не привязанную к среде. И в ней есть так же примеры с USB. И вот, на фоне всего этого я представил что STM32 ничем не сложнее меги, вооружившись таблицей разметки памяти с адресами регистров начинаю писать еще один вариант стандартной библиотеки для STM32. Гораздо более наивный, но зато компактный. Смыл был однозначно, я вообще считаю что если не понравился любой стандартный продукт, то это повод переписать его полностью, хотя вы можете со мной не согласиться.

MAKE

Вот прям так с нуля, в руках текстовый редактор (Kate), компилятор и собственно сам камень. Чем то напоминает прошлое, какие нибудь 70е, никаких тебе лабораторий с кучей цифровых осциллографов отладочных плат и мониторов, сходил на базар (алик) за камушком расположился в гараже или уютненькой комнатке и вот он билет в IT. Весь тулчейн уже появился в репозиториях Ubuntu, пакеты: gcc-arm-none-eabi libnewlib-arm-none-eabi gdb-multiarch build-essential stlink-tools. Из самого необходимого для сборки:

Насчет стартапов и компоновщика помогли комментарии в коде библиотеки CMSIS (позиционируется как стандарт взаимодействия с ядром ARM), для make помог хелп на сайте ARM. По итогу получился 50~100 строчный мейк, гораздо компактнее чем все примеры на гитхабе, в который вполне можно добавить и стандартный STM32 HAL (проверял, собиралось). В startup нужно добавить адреса векторов прерываний из datasheet, в system написать функцию включения кварцевого генератора, в компоновщике заполнить адреса памяти, в мейке все это собрать. А также на первое время, пока не написан API (HAL) понадобятся регистры это почти весь datasheet, 5к+ строчек. И вот уже можно хеллоувордить и мигать диодиком, но мы пойдем дальше.

Железо

st-link так же можно заказать на алике, но я паял сам, схема взята с документации по отладочным платам discovery. Не сбоящая на клонах прошивка скачана с форума ST. Все исходники для повторения на github. Помните шутку про драйвера дисковода на диске, так вот плата адаптирована под начальную прошивку через UART.

В остальных платах ничего примечательного нету, стандартная обвязка контроллера, одна плата однослойная вторая двухслойная, изготовлены в домашних условиях пленочным фоторезистом и трафаретом распечатанном на принтере. С применением 0,8 мм заклепок в качестве переходных отверстий. По разводке примечательно то что можно не брезговать выводить контакты из под чипа через неиспользуемые порты, главное не подать на них случайно единицу в коде, в остальном такой грязный хак помогает получить очень компактные платы. Схемы лежат в папке result в репозитории проекта.

USB

Как приятно жить во времена международных стандартов, никаких коммерческих тайн и прочей копирастии. Можно просто пойти на USB Implementers Forum и скачать там первоисточник стандарта. Пока писал ядро USB, с одной стороны могу сказать что понадобился весь томик стандарта, с другой только 9 раздел (конфигурационные запросы драйвера). В общем тут как и со всеми новыми концептуальными навороченными абстракциями: уровней вложенности, блок схем, новых терминов на увесистый том размером с аналоговую энциклопедию, а кода на пять килобайт. Все равно по итогу шина, либо пихаешь туда данные, либо забираешь из буфера по прерыванию. А все остальное как то пишется один раз, лишь бы система подхватила, а потом забывается и просто подключается в виде библиотечки. Хотя местами не хватило стандарта, понадобился ещё хелп ядра linux и хелп microsoft, нужно же понимать как понимают тот же самый стандарт программисты ОС. По итогу удалось реализовать следующие типы стандартных USB устройств:

  • геймпад

  • клавиатура (на самом деле USB ИК приемник, но драйвер от клавиатуры)

  • переходник USB-UART

  • Программатор флешек BIOS (работает поверх USB-UART)

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

Отладка

Многие любители мощных сред (Keil, ST Cube) и готовых комплектов отладочных плат считают главным преимуществом наличие отладчика. Но отладка возможна и в консоли по трем проводкам. Например для выяснения ошибок в момент инициализации USB. Я пользовался консольным GDB. Сложив весь поток данных в глобальный массив я считал его с помощью gdb. Так же и прослеживал за регистрами складывая их в цикле в переменные.

В тему документации все pdfки лежат в репозитории. Для написания дескрипторов USB HID устройств понадобилась стандартная утилита, раньше работала под вайном теперь в виртуальной XP.

Результат

Геймпад вполне играбелен, прошел на нем одну трассу в NFS, пультом удобно слушать музыку с youtube пока делаешь дела по дому. Программатором зашил таблетку от жадности для принтера. Демонстрацию работы всех устройств вы можете наблюдать на видео, расположены в соответствии со списком.

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


ссылка на оригинал статьи https://habr.com/ru/post/724800/

Анализ подбора гиперпараметров при обучении нейронной сети прямого распространения — FNN (на примере MNIST)

Для чего

При изучении нейронных сетей встречается много новой теории и новых терминов. Это усваивается сильно лучше, если некоторое время «поиграть с параметрами». Поэтому мы взяли простой широкоизвестный датасет (MNIST, изображения рукописных цифр), простую однослойную FNN (Нейронная сеть прямого распространения) и подвигали параметры в разные стороны, отмечая и сравнивая, что происходит.

Конечно, непосредственно для распознавания и классификации изображений лучше применять не FNN (Нейронные сети прямого распространения), а CNN (Сверточные нейронные сети), в том числе и предобученные. С этим согласны, и целью данной статьи не является попытка превысить на FNN точность распознавания на CNN. В данной статьи мы просто подвигаем гиперпараметры и сделаем соответствующих выводы.

Полученные выводы могут быть действительно полезны и применимы впоследствии, когда для решения соответствующих задач будет необходимо «тюнить» предобученные сверточные сети — менять штатный классификатор (последние слои) на свой — в этом случае как раз и пригодится понимание применения гиперпараметров.

Общий подход

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

Подготовка данных

Подготовка данных происходит абсолютно стандартно, как и в большинстве роликов и статей по MNIST, поэтому особо не комментируем, просто прикладываем.

Библиотеки
import numpy as np import matplotlib.pyplot as plt %matplotlib inline  import keras  from keras.models import Sequential from keras.layers import Dense, Flatten, Dropout from keras.datasets import mnist  from keras.callbacks import ReduceLROnPlateau from keras.callbacks import ModelCheckpoint 

Скачивание и преобразование данных
# Скачиваем данные и сразу распределяем на обучающую и тестовую выборки (x_train, y_train), (x_test, y_test) = mnist.load_data()  # Преобразуем данные в интервал [0,1] x_train = x_train / 255 x_test = x_test / 255  y_train_cat = keras.utils.to_categorical(y_train, 10) y_test_cat = keras.utils.to_categorical(y_test, 10)

Проверка корректности данных
# проверяем формы print(x_train.shape, y_train.shape, x_test.shape, y_test.shape)  # выводим выбранный вектор данных из обучающей выборки print(x_train[7777])  выводим метку класса print(y_train[7777])  # выводим метку класса в one-hot-codding print(y_train_cat[7777])  # Выводим выбранное изображение plt.imshow(x_train[7777], cmap='binary') plt.axis('off') # без осей plt.show()  # Выводим первых 25 изображений из обучающей выборки plt.figure(figsize=(20,10)) for i in range(25):     plt.subplot(5,5,i+1)     plt.xticks([])     plt.yticks([])     plt.imshow(x_train[i], cmap=plt.cm.binary)  plt.show()

Базовая комплектация сети

Берем самый простой стандартный вариант:

FNN: входной слой, один скрытый слой, выходной слой;
на выходе 10 нейронов (потому что 10 цифр = 10 классов);
функции активации — «relu» и «softmax» (стандарт);
функция потерь — «categorical_crossentropy» (стандарт, когда несколько классов);
метрика качества — «accuracy» (стандарт);
оптимизатор — «adam» (стандарт);
размер батча — 32 (надо с чего-то начинать);
размер валидационной выборки — 20% (стандарт).

Запускаем первый раз на 20 эпох и смотрим, что к чему.

model = keras.Sequential([     Flatten(input_shape=(28, 28, 1)),     Dense(128, activation='relu'),     Dense(10, activation='softmax')     ])  model.compile(     optimizer='adam',     loss='categorical_crossentropy',     metrics=['accuracy']     )  history = model.fit(     x_train, y_train_cat,      batch_size=32,      epochs=20,      validation_split=0.2     )

Выводим цифры …

print('train:', model.evaluate(x_train, y_train_cat, verbose = 0)) print('test:', model.evaluate(x_test, y_test_cat, verbose = 0)) print( 'val_loss:', np.argmin(history.history['val_loss']), history.history['val_loss'][np.argmin(history.history['val_loss'])] ) print( 'val_accuracy:', np.argmax(history.history['val_accuracy']), history.history['val_accuracy'][np.argmax(history.history['val_accuracy'])] )

train: [0.023798486217856407, 0.9946833252906799]
test: [0.09774788469076157, 0.9769999980926514]
val_loss: 7 0.08434651792049408
val_accuracy: 18 0.9779166579246521

… и графики.

plt.plot(list(range(1,len(history.history['loss'])+1)),history.history['loss']) plt.plot(list(range(1,len(history.history['val_loss'])+1)),history.history['val_loss']) plt.title("loss and val_loss") plt.grid(True) plt.show()
plt.plot(list(range(1,len(history.history['accuracy'])+1)),history.history['accuracy']) plt.plot(list(range(1,len(history.history['val_accuracy'])+1)),history.history['val_accuracy']) plt.title("accuracy and val_accuracy") plt.grid(True) plt.show()

В первом приближении видим, что все «как по учебнику»:

ошибка на обучающей выборке уменьшается;
ошибка на валидационной выборке сначала уменьшается, потом увеличивается — переобучение;
точность на обучающей выборке подходит вплотную к 1.0;
точность на валидационной выборке доходит до 0.978;
точность на тестовой выборке 0.977.

Отсюда начинаем перебирать параметры.

128 признаков, батчи 16-32-64-128

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

Точность на тестовой выборке у всеx очень близкая,
находится в интервале 0.9769-0.9789,
разница в десятые и сотые доли процента.

batch16: 0.9771999716758728
batch32: 0.978600025177002
batch64: 0.9789000153541565
batch128: 0.9768999814987183

256 признаков, батчи 16-32-64-128

Принципиально, ситуация та же, что и при 128 признаках.
Существенных изменений при переходе от 128 признаков к 256 не наблюдается.

Точность на тестовой выборке также очень близко,
находится в интервале 0.9795-0.9810,
что по сравнению с 0.9769-0.9789 (на 128) практически там же.

batch16: 0.9794999957084656
batch32: 0.9801999926567078
batch64: 0.9785000085830688
batch128: 0.9810000061988831

512 признаков, батчи 16-32-64-128

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

Точность на тестовой выборке также очень близко,
находится в интервале 0.9783-0.9824
что по сравнению с 0.9795-0.9810 (256) и 0.9769-0.9789 (128) практически там же,
хотя видно, что с увеличением числа признаков точность все таки на чуть-чуть повышается.

batch16: 0.9789000153541565
batch32: 0.9782999753952026
batch64: 0.9815000295639038
batch128: 0.9824000000953674

1024 признака, батчи 16-32-64-128

Ну и совсем для ясности сделаем замер на 1024 признака, и видим все то же самое.
Прогиб ошибки смещается левее из-за увеличившегося количества признаков, и смещается правее по мере увеличения размера батча.

Точность на тестовой выборке также очень близко,
находится в интервале 0.9806-0.9841.

batch16: 0.9805999994277954
batch32: 0.9811999797821045
batch64: 0.982200026512146
batch128: 0.9840999841690063

Теперь заметнее, что при увеличении количества признаков точность в целом повышается:

128: 0.9769-0.9789
256: 0.9795-0.9810
512: 0.9783-0.9824
1024: 0.9806-0.9841.

Объединяем полученные данные

Ошибка (‘loss’) на тестовой выборке:

Признаки\Батчи

16

32

64

128

128

0.1382

0.1004

0.0930

0.0860

256

0.1268

0.1047

0.0843

0.0742

512

0.1393

0.1253

0.0860

0.0717

1024

0.1615

0.1267

0.0957

0.0704

Точность (‘accuracy’) на тестовой выборке:

Признаки\Батчи

16

32

64

128

128

0.9772

0.9786

0.9789

0.9769

256

0.9795

0.9802

0.9785

0.9810

512

0.9789

0.9783

0.9815

0.9824

1024

0.9806

0.9812

0.9822

0.9841

Предварительные выводы по количеству признаков и размерам батчей

Предварительные выводы следующие:

  1. С увеличением размера батча линии валидационной выборки становятся плавнее.

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

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

  4. С увеличением размера батча ошибка тестовой выборки в целом уменьшается.

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

  6. С увеличением количества признаков ошибка тестовой выборки сначала падает, а потом или продолжает падать или начинает расти — переобучение. При этом чем меньше размер батча, тем выше уходит ошибка, и чем больше размер батча, тем меньше ошибка и может продолжать снижаться. То есть при увеличении количества признаков ошибка может уменьшаться при соответствующем подборе размере батча.

  7. С увеличением количества признаков точность тестовой выборки в целом увеличивается, и это зависит от переобученности и размера батча.

  8. Самые лучшие результаты в данном примере наблюдаются при максимальном количестве признаков (1024) по мере увеличения размера батча.

Берем сразу большой батч (batch1024)

На примерах выше видно, как линии сглаживаются при увеличении размера батча, а на размере 128 ошибка как бы зависает. Так и хочется взять батч еще побольше, чтобы ошибка двигалась вниз. Делаем скачок и берем размер батча 1024.

Можно сказать, что все ожидаемо:
1. линии гладкие;
2. ошибка валидационной выборки постепенно снижается;
3. линии ошибки идут ровненько одна за другой — чем больше батч, тем ниже линия:
4. линии точности также идут ровненько одна за другой — чем больше батч, тем выше линия

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

Также видно, что здесь уже не хватает эпох и оптимум может быть где-то правее.

Таким образом становится понятен общий подход к подбору гиперпараметров:
1. увеличивать количество признаков;
2. под новое количество признаков подстраивать размер батча, чтобы сдвигать переобучение на более поздний срок;
3. отслеживать, когда происходит переобучение.

Альтернативный вариант — представить нейросеть, осуществляющую распознавание и классификацию, как функцию от соответствующих гиперпараметров, и надстроить отдельную «управляющую» нейронную сеть, определяющую оптимум данной функции стандартным для нейросети способом. Подбор гиперпарметров с помощью «управляющей» нейросети — тема для отдельной статьи.

Добавляем уменьшение шага

Запустим обучение с batch128 сразу на 100 эпох,
количество показателей 128-512-2048-8192.

Конечно, будет переобучение.
Существует функция, сохраняющая лучшие результаты (ModelCheckpoint). Однако наша задача — академическая, поэтому сохранять лучший результат не будем, просто смотрим графики 100 эпох, функцию прикладываем на случай, если понадобится.

сheckpoint = ModelCheckpoint('mnist-fnn.h5',                                monitor='val_acc',                                save_best_only=True,                               verbose=1)

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

reduce_on_plateau = ReduceLROnPlateau(monitor='val_accuracy',                                              patience=3,                                              verbose=1,                                              factor=0.5,                                              min_lr=0.0000001                                       )

Из нескольких тестируемых комбинаций лучшая точность была 0.9869 (8192 признака, уменьшение шага в два разе, если в течение 3 эпох точность не увеличивается, ограничение на минимальный шаг обучение e-07).

Добавляем Dropout

Для сравнения:

20 эпох 1024 признака давали следующие значения точности :
batch16: 0.9806
batch32: 0.9812
batch64: 0.9822
batch128: 0.9841

100 эпох batch128 с уменьшением шага обучения давали следующие значения точности :
128 признаков: 0.9787
512 признаков: 0.9822
2048 признаков: 0.9849
8192 признака: 0.9869

С применением Dropout (0.1-0.2-0.3-0.4-0.5) 100 эпох, 1024 признака, batch128, без уменьшения шага — точность 0.9854 на Dropout 0.5.

За несколько итераций точность повысилась до 0.9860
(4096 признаков, batch512, Dropout 0.8, уменьшение шага в 2 раза, если 3 эпохи точность не поднимается).

Изначально допускаем, что во всех представленных случаях параметры не оптимальные, но вполне могут применяться для совместного сравнения.

Таким образом в ряде случаев (но не во всех) применение Dropout дает точность выше, чем без Dropout в случае переобучения, и сравнимую с уменьшением шага обучения при схожих количествах признаков и размерах батчей. Представляется, что наиболее вероятный ориентир для применения Dropout — уменьшение вычислений и соответствующее сокращение времени при достижении приемлемых и не самых высоких показателей точности.

Выводы

Выводы получились интересные.

1. Сеть мгновенно доходит до точности 97,7-97.8%, и такая точность достигается «легко и сразу».

2. Следующий 1% точности (до 98.7%) достигается уже с трудом, нужно подбирать.

3. Забегая вперед, можно сказать, что CNN дают точность выше 99.5%, но этот «крайний» процент (выше 99%) достигается уже с очень большим трудом.

Таким образом видно, что определенная «пороговая» точность (в данном примере порядка 97.7%) достигается достаточно уверенно и стабильно, и за этот показатель можно смело подписываться и заключать контракт, а вот после этого значения каждая десятая и даже сотая доля процента может потребовать существенных усилий.

Касательно общего алгоритма подбора краткий вывод получился такой:
Нужно брать количество признаков «побольше» (насколько позволяют вычислительные мощности и время) и следить, чтобы сеть не переобучалась. Для контроля переобучения подбирать размер батча и dropout, а для окончательного «шлифования» подбирать схему уменьшения шага обучения.

Что дальше

Вариантом развития представляется тестирование FNN в несколько слоев, а также тестирование CNN в несколько слоев с разными размерами ядер.

Отдельным вариантом развития является создание «управляющей» нейронной сети, которая «самостоятельно» будет подбирать архитектуру и гиперпараметры «контролируемых» нейронных сетей.


ссылка на оригинал статьи https://habr.com/ru/post/724418/

Создание карты потоков с помощью JavaScript: пошаговое руководство


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

В текущем руководстве мы познакомим вас с процессом создания карты потоков. А чтобы сделать этот процесс более практичным, мы используем пример визуализации количества студентов из Индии, обучающихся в разных странах. Следуя этому руководству, вы научитесь создавать собственные карты потоков с помощью JS для любых видов данных.

Превью карты

Ниже вы можете видеть, как будет выглядеть наша итоговая карта потоков.

Создание базовой карты потоков с помощью JavaScript

С помощью JS построить карту потоков можно в четыре основных шага:

  1. Создать веб-страницу с помощью HTML.
  2. Включить необходимые файлы JS.
  3. Добавить данные.
  4. Написать JS-код для отрисовки графика.

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

▍ 1. Создание веб-страницы на HTML

Для начала создадим простую HTML-страницу в качестве контейнера для отображения нашей карты. Сделаем мы это путём создания элемента <div> и установки его атрибута id на container. Это позволит позднее обращаться к созданному контейнеру, когда мы добавим карту.

Чтобы карта потоков отображалась на всю страницу, мы с помощью CSS установим высоту и ширину <div> на 100%. Можете подстроить стилизацию на своё усмотрение.

<!DOCTYPE html> <html lang="en">   <head>     <meta charset="utf-8">     <title>Flow Map</title>     <style type="text/css">             html, body, #container {         width: 100%;         height: 100%;         margin: 0;         padding: 0;       }     </style>   </head>   <body>     <div id="container"></div>   </body> </html>

▍ 2. Добавление необходимых файлов JavaScript

Далее мы добавим в раздел шапки нашего HTML-файла необходимые скрипты для построения карты потоков.

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

Для создания карты нам нужно добавить несколько модулей AnyChart: модули Core и Geo Map для визуализации данных, а также geodata для карты. Для обработки географических координат мы используем библиотеку Proj4js, так что она тоже включена. Кроме того, для добавления в UI возможности масштабирования нам потребуется AnyChart модуль Common UI и CSS.

<!DOCTYPE html> <html lang="en">   <head>     <meta charset="utf-8">     <title>Flow Map</title>     <script src="https://cdn.anychart.com/releases/8.11.0/js/anychart-core.min.js"></script>     <script src="https://cdn.anychart.com/releases/8.11.0/js/anychart-map.min.js"></script>     <script src="https://cdn.anychart.com/releases/8.11.0/geodata/custom/world/world.js"></script>     <script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.8.0/proj4.js"></script>     <script src="https://cdn.anychart.com/releases/8.11.0/js/anychart-ui.min.js"></script>     <link rel="stylesheet" href="https://cdn.anychart.com/releases/8.11.0/css/anychart-ui.min.css">     <link rel="stylesheet" href="https://cdn.anychart.com/releases/8.11.0/fonts/css/anychart-font.min.css">     <style type="text/css">             html, body, #container {         width: 100%;         height: 100%;         margin: 0;         padding: 0;       }     </style>   </head>   <body>     <div id="container"></div>     <script>       // Здесь будет находиться JS-код карты потоков.     </script>   </body> </html>

▍ 3. Добавление данных

В этом руководстве мы будем визуализировать Топ-10 стран, в которые направлялись студенты из Индии в 2022 году. Источником данных здесь выступает индийское Министерство иностранных дел, которое предоставляет информацию по количеству студентов, обучающихся за рубежом.

Чтобы отобрать 10 ведущих стран, я проанализировал данные и на основе количества учащихся в них студентов выбрал следующие: Австралия, Канада, Германия, Оман, Катар, Российская Федерация, Саудовская Аравия, Объединённые Арабские Эмираты, Великобритания и США.

При создании карты потоков в качестве конечных точек следования учащихся мы будем использовать столицы этих стран, а в качестве отправной – Нью Дели, Индия. Координаты широты и долготы столиц я определял с помощью сайта LatLong.net. В таблице ниже показан список выбранных стран, а также общее число обучающихся в них студентов из Индии и координаты столиц.

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

var data = [   { points: [28.610198, 77.207584, -30.59, 145.94], to: "Australia", total: 100009 },   { points: [28.610198, 77.207584, 45.41, -75.69], to: "Canada", total: 183310 },   { points: [28.610198, 77.207584, 52.51, 13.40], to: "Germany", total: 34864 },   { points: [28.610198, 77.207584, 23.58, 58.38], to: "Oman", total: 39550 },   { points: [28.610198, 77.207584, 25.28, 51.53], to: "Qatar", total: 46000 },   { points: [28.610198, 77.207584, 55.74, 37.62], to: "Russian Federation", total: 18039 },   { points: [28.610198, 77.207584, 24.71, 46.67], to: "Saudi Arabia", total: 65800 },   { points: [28.610198, 77.207584, 24.45, 54.37], to: "United Arab Emirates", total: 164000 },   { points: [28.610198, 77.207584, 52.66, -0.95], to: "United Kingdom", total: 55465 },   { points: [28.610198, 77.207584, 38.88, -77.03], to: "United States", total: 465791 } ];

▍ 4. Написание JS-кода для отрисовки карты

Теперь, когда данные подготовлены, пора перейти к основной части – размещению карты потоков на веб-странице.

Прежде чем начать, нужно убедиться, что наш код выполняется всего раз, и страница загружается полноценно. Сделаем мы это с помощью функции anychart.onDocumentReady(), которая включит весь код карты потоков.

<script>   anychart.onDocumentReady(function () {     // Здесь будет находиться код карты потоков.   }); </script>

Далее мы добавим данные, которые подготовили на первом этапе.

anychart.onDocumentReady(function () {   var data = [     { points: [28.610198, 77.207584, -30.59, 145.94], to: "Australia", total:    100009 },     { points: [28.610198, 77.207584, 45.41, -75.69], to: "Canada", total: 183310 },     { points: [28.610198, 77.207584, 52.51, 13.40], to: "Germany", total: 34864 },     { points: [28.610198, 77.207584, 23.58, 58.38], to: "Oman", total: 39550 },     { points: [28.610198, 77.207584, 25.28, 51.53], to: "Qatar", total: 46000 },     { points: [28.610198, 77.207584, 55.74, 37.62], to: "Russian Federation", total: 18039 },     { points: [28.610198, 77.207584, 24.71, 46.67], to: "Saudi Arabia", total: 65800 },     { points: [28.610198, 77.207584, 24.45, 54.37], to: "United Arab Emirates", total: 164000 },     { points: [28.610198, 77.207584, 52.66, -0.95], to: "United Kingdom", total: 55465 },     { points: [28.610198, 77.207584, 38.88, -77.03], to: "United States", total: 465791 }   ]; });

Для создания графика карты потоков мы используем AnyChart функцию connector(), передав ей данные для соединительных линий и включив геоданные карты мира.

var map = anychart.connector(); var connectorSeries = map.connector(data); map.geoData(anychart.maps.world);

Далее мы установим метки и добавим стрелочки в конец линии каждого потока.

connectorSeries   .labels()   .enabled(true)   .position('100%')   .format(function () {     return this.getData('to')   });  connectorSeries   .markers()   .position('100%')   .size(12);

Теперь нужно указать для нашей карты потоков заголовок.

map.title("Top 10 Destination Countries for Indian Students Studying Abroad");

Наконец, мы сошлёмся на ID контейнера, поместим карту в элемент container и отобразим его на странице с помощью функции draw().

map.container('container'); map.draw();

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

И это совсем несложно – потребуется всего три дополнительные строчки кода.

var zoomController = anychart.ui.zoom(); zoomController.target(map); zoomController.render();

Вот и всё! Наша базовая карта потоков готова.

Взгляните на её интерактивную версию и поиграйтесь с ней в песочнице AnyChart. Вот весь исходный код:

<!DOCTYPE html> <html lang="en">   <head>     <meta charset="utf-8">     <title>JavaScript Flow Map</title>     <script src="https://cdn.anychart.com/releases/8.11.0/js/anychart-core.min.js"></script>     <script src="https://cdn.anychart.com/releases/8.11.0/js/anychart-map.min.js"></script>     <script src="https://cdn.anychart.com/releases/8.11.0/geodata/custom/world/world.js"></script>     <script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.8.0/proj4.js"></script>     <script src="https://cdn.anychart.com/releases/8.11.0/js/anychart-ui.min.js"></script>     <link rel="stylesheet" href="https://cdn.anychart.com/releases/8.11.0/css/anychart-ui.min.css">     <link rel="stylesheet" href="https://cdn.anychart.com/releases/8.11.0/fonts/css/anychart-font.min.css">     <style type="text/css">             html, body, #container {         width: 100%;         height: 100%;         margin: 0;         padding: 0;       }     </style>   </head>   <body>     <div id="container"></div>     <script>       anychart.onDocumentReady(function () {         // Установка данных целевых стран.         var data = [           { points: [28.610198, 77.207584, -30.592659, 145.943667], to: "Australia", total: 100009 },           { points: [28.610198, 77.207584, 45.411673, -75.69629], to: "Canada", total: 183310 },           { points: [28.610198, 77.207584, 52.511693, 13.403121], to: "Germany", total: 34864 },           { points: [28.610198, 77.207584, 23.5880, 58.3829], to: "Oman", total: 39550 },           { points: [28.610198, 77.207584, 25.2854, 51.5310], to: "Qatar", total: 46000 },           { points: [28.610198, 77.207584, 55.747362, 37.621273], to: "Russian Federation", total: 18039 },           { points: [28.610198, 77.207584, 24.710437, 46.675164], to: "Saudi Arabia", total: 65800 },           { points: [28.610198, 77.207584, 24.4539, 54.3773], to: "United Arab Emirates", total: 164000 },           { points: [28.610198, 77.207584, 52.667078, -0.955920], to: "United Kingdom", total: 55465 },           { points: [28.610198, 77.207584, 38.884053, -77.033513], to: "United States", total: 465791 },         ]         // Создание графика карты.         var map = anychart.connector();         // Добавление геоданных карты мира.         map.geoData(anychart.maps.world);         // Создание соединительных линий.         var connectorSeries = map.connector(data);         // Установка меток для соединительных линий.         connectorSeries           .labels()           .enabled(true)           .position('100%')           .format(function () {             return this.getData('to')           });         // Установка стрелочек в конце линий.         connectorSeries           .markers()           .position('100%')           .size(12);         // Добавление возможности масштабирования.         var zoomController = anychart.ui.zoom();         zoomController.target(map);         zoomController.render();         // Установка названия карты.         map.title('Top 10 Destination Countries for Indian Students Studying Abroad');         // Установка контейнера.         map.container('container');         // Отрисовка карты.         map.draw();       });     </script>   </body> </html>

Кастомизация

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

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

▍ A. Оптимизация кривых линий и позиций меток

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

Чтобы это исправить, можно изменить датасет, добавив в него свойство curvature, которое позволит скорректировать кривизну отдельных линий. Помимо этого, мы добавим свойство label и установим его значения, чтобы метки отображались в нужных точках карты.

var data = [   { points: [28.610198, 77.207584, -30.592659, 145.943667], to: "Australia", total: 100009, curvature: 0.5 },   { points: [28.610198, 77.207584, 45.411673, -75.69629], to: "Canada", total: 183310, curvature: 0.8, label: { offsetY: -30 } },   { points: [28.610198, 77.207584, 52.511693, 13.403121], to: "Germany", total: 34864, curvature: 0.3, label: { anchor: 'center-top', offsetY: -2 } },   { points: [28.610198, 77.207584, 23.5880, 58.3829], to: "Oman", total: 39550, curvature: -0.5, label: { anchor: 'left-top' } },   { points: [28.610198, 77.207584, 25.2854, 51.5310], to: "Qatar", total: 46000, curvature: 0.4, label: { anchor: 'right-top', offsetY: -20 } },   { points: [28.610198, 77.207584, 55.747362, 37.621273], to: "Russian Federation", total: 18039, curvature: 0.4, label: { anchor: 'left-bottom' } },   { points: [28.610198, 77.207584, 24.710437, 46.675164], to: "Saudi Arabia", total: 65800, curvature: 0.7, label: { offsetY: -5 } },   { points: [28.610198, 77.207584, 24.4539, 54.3773], to: "United Arab Emirates", total: 164000, curvature: 0, label: { anchor: 'left-top', offsetY: -15 } },   { points: [28.610198, 77.207584, 52.667078, -0.955920], to: "United Kingdom", total: 55465, curvature: 0.4, label: { anchor: 'right-top', offsetY: -25, offsetX: -10 } },   { points: [28.610198, 77.207584, 38.884053, -77.033513], to: "USA", total: 465791, curvature: -0.6 }, ];

Благодаря этим изменениям, теперь можно отображать метки даже в случае их наложения. Реализовать это можно одной строкой кода.

map.overlapMode("allow-overlap");

▍ B. Доработка справочной информации

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

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

connectorSeries   .tooltip()   .useHtml(true)   .format(function () {     return (       '<h5 style="font-size:12px; font-weight:400; margin: 0.25rem 0;">To:<b> ' + this.getData('to') + '</b></h5>' +       '<h5 style="font-size:12px; font-weight:400; margin: 0.25rem 0;">Total Students: <b>' + this.getData('total').toLocaleString() + '</b></h5>'     );   });

▍ C. Доработка заголовка

Чтобы повысить привлекательность заголовка нашей карты, можно выделить его при помощи HTML-стилизации, чтобы он был более заметен и привлекал внимание.

map.title()   .enabled(true)   .useHtml(true)   .text(     '<span style="font-size:15px;font-weight:600;">Top 10 Destination Countries for Indian Students Studying Abroad</span><br/>' +     '<span style="font-size: 14px;">The top destination is the U.S.</span>'   );

▍ D. Выбор цветов для соединительных линий и маркеров

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

Для этого мы изменим создающий линии код, и применим к ним новый цвет с помощью функций fill() и stroke().

var connectorSeries = map.connector(data)   .fill(['#e8dd09', 'red'])   .stroke(['#e8dd09', 'red']); connectorSeries   .hovered()   .stroke('#808080')   .fill('#808080'); connectorSeries   .hovered()   .markers()   .stroke('#808080')   .fill('#808080');

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

▍ E. Изменение цвета и толщины линий на основе данных

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

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

  • менее 50,000;
  • от 50,000 до 100,000;
  • от 100,000 до 200,000;
  • больше 200,000.

Мы также создадим filter_function(), которая будет фильтровать данные согласно этим категориям.

function filter_function(val1, val2) {   if (val2)     return function (fieldVal) {       return val1 <= fieldVal && fieldVal < val2;     };     else       return function (fieldVal) {         return val1 <= fieldVal;       }; }

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

function createSeries(data, name, color, size) {    // Создание соединительных линий.   var connectorSeries = map.connector(data)     .name(name)     .fill(color)     .stroke(color)     .color(color);   connectorSeries     .hovered()     .stroke('#808080')     .fill('#808080');   connectorSeries     .hovered()     .markers()     .stroke('#808080')     .fill('#808080');    // Установка меток для линий.   connectorSeries     .labels()     .enabled(true)     .position('100%')     .fontColor('#2D2D2D')     .format(function () {       return this.getData('to')     });    // Установка стрелочек в конце линий.   connectorSeries     .markers()     .position('100%')     .size(12);    // Настройка справочной информации для линий.   connectorSeries     .tooltip()     .useHtml(true)     .format(function () {       return (         '<h5 style="font-size:12px; font-weight:400; margin: 0.25rem 0;">To:<b> ' + this.getData('to') + '</b></h5>' +         '<h5 style="font-size:12px; font-weight:400; margin: 0.25rem 0;">Total Students: <b>' + this.getData('total').toLocaleString() + '</b></h5>'       );     });    // Установка толщины линии, исходя из количества студентов.   connectorSeries      .startSize(size[0])      .endSize(size[1]); }

Мы также создадим из наших данных датасет.

var dataSet = anychart.data.set(data).mapAs();

После чего отфильтруем его с помощью filter_function() и передадим в функцию createSeries() для создания набора линий.

createSeries(dataSet.filter('total', filter_function(0, 50000)), 'Less than 50,000', '#A149FA', [1, 0]);  createSeries(dataSet.filter('total', filter_function(50000, 100000)), '50,000 - 100,000', '#3B44F6', [2, 1]);  createSeries(dataSet.filter('total', filter_function(100000, 200000)), '100,000 - 200,000', '#3EC70B', [4, 1]);  createSeries(dataSet.filter('total', filter_function(200000, 500000)), 'More than 200,000', '#F7EC09', [6, 1]);

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

map.legend().enabled(true).position('bottom').padding([20, 0, 0, 0]).fontSize(10);  map.legend().title().enabled(true).text('Number of Students').fontSize(13).padding([0, 0, 5, 0]);

Итак, цель достигнута! Мы создали прекрасную интерактивную карту потоков с помощью JS.

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

Вот весь код карты:

<!DOCTYPE html> <html lang="en">   <head>     <meta charset="utf-8">     <title>JavaScript Flow Map</title>     <script src="https://cdn.anychart.com/releases/8.11.0/js/anychart-core.min.js"></script>     <script src="https://cdn.anychart.com/releases/8.11.0/js/anychart-map.min.js"></script>     <script src="https://cdn.anychart.com/releases/8.11.0/geodata/custom/world/world.js"></script>     <script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.8.0/proj4.js"></script>     <script src="https://cdn.anychart.com/releases/8.11.0/js/anychart-ui.min.js"></script>     <link rel="stylesheet" href="https://cdn.anychart.com/releases/8.11.0/css/anychart-ui.min.css">     <link rel="stylesheet" href="https://cdn.anychart.com/releases/8.11.0/fonts/css/anychart-font.min.css">     <style type="text/css">             html, body, #container {         width: 100%;         height: 100%;         margin: 0;         padding: 0;       }     </style>   </head>   <body>     <div id="container"></div>     <script> anychart.onDocumentReady(function () {   // Установка данных для целевых стран.   var data = [     { points: [28.610198, 77.207584, -30.592659, 145.943667], to: "Australia", total: 100009, curvature: 0.5 },     { points: [28.610198, 77.207584, 45.411673, -75.69629], to: "Canada", total: 183310, curvature: 0.8, label: {offsetY: -30} },     { points: [28.610198, 77.207584, 52.511693, 13.403121], to: "Germany", total: 34864, curvature: 0.3, label: {anchor: 'center-top', offsetY: -2} },     { points: [28.610198, 77.207584, 23.5880, 58.3829], to: "Oman", total: 39550, curvature: -0.5, label: {anchor: 'left-top'} },     { points: [28.610198, 77.207584, 25.2854, 51.5310], to: "Qatar", total: 46000, curvature: 0.4, label: {anchor: 'right-top', offsetY: -20} },     { points: [28.610198, 77.207584, 55.747362, 37.621273], to: "Russian Federation", total: 18039, curvature: 0.4, label: {anchor: 'left-bottom'} },     { points: [28.610198, 77.207584, 24.710437, 46.675164], to: "Saudi Arabia", total: 65800, curvature: 0.7, label: {offsetY: -5} },     { points: [28.610198, 77.207584, 24.4539, 54.3773], to: "United Arab Emirates", total: 164000, curvature: 0, label: {anchor: 'left-top', offsetY: -15} },     { points: [28.610198, 77.207584, 52.667078, -0.955920], to: "United Kingdom", total: 55465, curvature: 0.4, label: {anchor: 'right-top', offsetY: -25, offsetX: -10} },     { points: [28.610198, 77.207584, 38.884053, -77.033513], to: "USA", total: 465791, curvature: -0.6 },   ]   // Создание графика карты.   var map = anychart.connector();   // Создание датасета из данных.   var dataSet = anychart.data.set(data).mapAs();   // Добавление геоданных карты мира.   map.geoData(anychart.maps.world);   // Создание 4 наборов и фильтрация данных по количеству студентов   createSeries(dataSet.filter('total', filter_function(0, 50000)), 'Less than 50,000', '#A149FA', [1, 0]);   createSeries(dataSet.filter('total', filter_function(50000, 100000)), '50,000 - 100,000', '#3B44F6', [2, 1]);   createSeries(dataSet.filter('total', filter_function(100000, 200000)), '100,000 - 200,000', '#3EC70B', [4, 1]);   createSeries(dataSet.filter('total', filter_function(200000, 500000)), 'More than 200,000', '#F7EC09', [6, 1]);   // Функция для создания и настройки наборов.   function createSeries(data, name, color, size) {     // Создание соединительных линий.     var connectorSeries = map.connector(data)       .name(name)       .fill(color)       .stroke(color)       .color(color);     connectorSeries       .hovered()       .stroke('#808080')       .fill('#808080');     connectorSeries       .hovered()       .markers()       .stroke('#808080')       .fill('#808080');     // Установка меток для линий.     connectorSeries       .labels()       .enabled(true)       .position('100%')       .fontColor('#2D2D2D')       .format(function () {         return this.getData('to')       });     // Установка стрелочек в конце линий.     connectorSeries       .markers()       .position('100%')       .size(12);     // Указание справочной информации для наборов.     connectorSeries       .tooltip()       .useHtml(true)       .format(function () {         return (           '<h5 style="font-size:12px; font-weight:400; margin: 0.25rem 0;">To:<b> ' + this.getData('to') + '</b></h5>' +           '<h5 style="font-size:12px; font-weight:400; margin: 0.25rem 0;">Total Students: <b>' + this.getData('total').toLocaleString() + '</b></h5>'           );       });     // Установка толщины соединительной линии, исходя из количества студентов.     connectorSeries       .startSize(size[0])       .endSize(size[1]);   }   // Добавление возможности масштабирования.   var zoomController = anychart.ui.zoom();   zoomController.target(map);   zoomController.render();   // Отображение всех меток, даже в случае наложения.   map.overlapMode("allow-overlap");   // Установка названия карты.   map.title()     .enabled(true)     .useHtml(true)     .text(       '<span style="font-size:15px;font-weight:600;">Top 10 Destination Countries for Indian Students Studying Abroad</span><br/>' +       '<span style="font-size: 14px;">The top destination is the U.S.</span>'     );   // Добавление легенды.   map.legend().enabled(true).position('bottom').padding([20, 0, 0, 0]).fontSize(10);   map.legend().title().enabled(true).text('Number of Students').fontSize(13).padding([0, 0, 5, 0]);   // Установка контейнера.   map.container('container');   // Отрисовка карты.   map.draw(); }); // Функция фильтрации данных. function filter_function(val1, val2) {   if (val2)     return function (fieldVal) {       return val1 <= fieldVal && fieldVal < val2;     };   else     return function (fieldVal) {       return val1 <= fieldVal;     }; }     </script>   </body> </html>

Заключение

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

Успехов!

Telegram-канал с розыгрышами призов, новостями IT и постами о ретроиграх 🕹️


ссылка на оригинал статьи https://habr.com/ru/company/ruvds/blog/724454/

Дизайн-система. Критикуем и предлагаем

Это вторая часть общей теории.

Дизайн-система — это набор компонентов, правил, предписаний и инструментов для повышения качества и скорости разработки продуктов, а также эффективной поддержки существующих продуктов.

Смысл использования дизайн-системы — сократить время на разработку и поддерживать несколько направлений разработки одновременно (от mobile до web), централизованно управляя макетами и оптимизируя время на редактировании и добавлении новых компонентов и стилей.

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

В классическом атомном дизайне есть пять уровней:

  • Атомы

  • Молекулы

  • Организмы

  • Шаблоны

  • Страницы

1. Атомы:
Атомы — основные строительные компоненты дизайн-системы. Сами по себе атомы бесполезны, и обретают смысл в комбинированных формах между собой.

Кнопка

Кнопка

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

Логотип + кнопка

Логотип + кнопка

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

Логотип + кнопка + текстовый блок формирует блок хэдэра

Логотип + кнопка + текстовый блок формирует блок хэдэра

4. Шаблоны
Шаблоны — группы организмов и/или организмов комбинированных с молекулами, формирующие страницы. На этом этапе становится видна общая картина. Именно шаблон позволяет видеть конечный дизайн. Шаблон содержит в себе условный контент.

5. Страницы
Страницы — это заполненные контентом экземпляры шаблонов. В них, для точной передачи того, что увидит пользователь, условный контент заменён настоящим. Страничный уровень полезен тем, что на нём можно видеть как работает дизайн-система с нижних уровней до уровня страниц.

Данная атомарная система является классическим подходом к созданию и управлению дизайн-системами на рынке. В нашей дизайн-системе и её представлении, были изменены некоторые структурные особенности. Атомарная система не выдерживает смысловой, терминологической критики, где есть атомы, молекулы, как метафора частиц материи, однако после метафоры частиц материи из физики/химии, идут “страницы” и “шаблоны”, что вносит некий когнитивное искажение в общую структуру картины атомарной системы дизайна. Так же, атомарная дизайн-система имеет ряд архитектурных упущений, которые выявились по ходу опыта использования на крупных продуктах, которые требуют более проработанной архитектуры.
О ней мы и поговорим.

Токены

Токены в дизайн-системе — это набор базовых переменных значений стилей (цвета, шрифты, тени и тд).

Смысл использования дизайн токенов — упростить процесс разработки и помочь настроить “язык общения” между дизайнерами и разработчиками с помощью системы наименований токенов (семантики токенов).

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

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

Дизайн-системы часто представлены в программах как библиотеки компонентов. Библиотеки однозначно полезны для быстрой сборки продуктов, но такие компоненты не рассказывают всей истории. Решение о том, какое значение где использовать, уже принято. Оно скрыто внутри компонента, невидимое для дизайнера. В таких случаях трудно понять, как построить новый компонент, который впишется в систему.

Вы не знаете, почему уведомление синее или почему основная кнопка зеленая. И вы не знаете, как убедиться, что новые компоненты обновятся, если изменится дизайн-система.

Несмотря на то, что компоненты удобны в использовании, они:

  • часто плохо документированы (зачем добавлять документы, можно просто скопировать и вставить компонент);

  • сложны для понимания и прослеживания связи (например, почему отступ равен 4px? Почему значение кнопки “blue”?);

  • обязывают интенсивно работать, чтобы сохранять их актуальными, когда дизайн-система меняется;

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

Решением проблем описанных выше служат дизайн-токены.О дизайн-токенах и настройки языка общения, мы поговорим в третьей части данного теоретического курса.

Иной взгляд

В нашем подходе, мы уходим от атомарной структуры, которая не выдерживает смысловой критики метафоры химических элементов, выдерживая её лишь до уровня “организм” он же “блок”. Заменяем атомарную метафору на более приземленные, относительные темы термины, которым даём четкие определения, объясняющие понятия и разграничивающие конструктивные особенности и смыслы обозреваемых сущностей.

1. Элемент дизайн-системы (токены):
Элемент дизайн-системы — это исходная сущность стиля используемая в компоненте (цвет, тень, шрифт, иконка и т.д.)

2. Компонент дизайн-системы:
Компонент в дизайн-системе — это составная часть более сложного объекта. В свою очередь, делится на:

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

  • Комбинированный компонент
    Составная часть простых компонентов, образующая комбинированную, но всё ещё самостоятельно не функционирующую часть системы. В своей структуре состоит из элементов и нескольких компонентов. (чекбокс/тогл/радиобатон с текстом, табы, пагинация, хлебные крошки и т.д.)

Примечание:
Функционирование — есть проявление поведенческой и деятельностной активности. Поведенческая деятельность в простом и комбинированных компонентах имеется на примере ховеров как минимум, но деятельной активности в отрыве от сценарного контекста и блочных/шаблонных структур — не имеет.

Простой компонент, как и комбинированный — не приобретает деятельностной активности, если находится вне логического блока или шаблона (смотреть п.4 Блоки и п.5 Шаблоны), соответственно не попадает в статус функционирующей части в полном объеме, поскольку не имеет в себе сценарного контекста определённого блочной структурой (блоком) или шаблоном, в которых могут приобретать деятельностную активность, как недостающую часть функционирования.

Иными словами — простой и комбинированный компонент, не существует отдельно от блоков или шаблонов, не является самостоятельно функционирующей частью системы вне блока/шаблона, как логически определённого и структурированного организма.

В свою очередь, когда компоненты определены в логические блоки (как определяются блоки в вёрстке) и шаблоны, эти наборы компонентов являют собой деятельную самостоятельно функционирующую часть системы.

Таким образом, например, кнопка, которая ранее являлась нефункционирующей частью в отрыве от логических блоков/шаблонов, в шаблоне модального окна приобретает статус функционирующей части системы, т.к. является частью логического блока системы, когда в отрыве от подобных логических блоков, кнопка не существует и не может существовать в полной мере, являясь функционирующей частью. Этапы блоков и шаблонов придают объединённым группам компонентов статус функционирующих частей системы. Так легче проводить смысловые границы и управлять дизайн-системой.

Простой и комбинированный компонент находится в категории “Компоненты” и настраивается по своей структуре в панели настроек компонента Figma, где приобретает необходимый нам вид и размер относительно среды разработки.

Компонент и бар настройки компонента (у красной черты)

Компонент и бар настройки компонента (у красной черты)

3. Группа компонентов:
Группа компонентов — простой или комбинированный компонент объединённый в группу своих копий, которая настраивается (количественно и по ориентации в настройках группы по инструментам Figma). Самостоятельно не функционирующая часть системы. В группу компонентов загоняются все простые и комбинированные компоненты. Между этими группами задаются отступы, которые контролируются на уровне групп компонентов.

Настройка отступов между простыми компонентами в группе

Настройка отступов между простыми компонентами в группе

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

Настройка верхнего отступа у группы компонентов

Настройка верхнего отступа у группы компонентов
Настройка верхнего отступа у группы компонентов

Настройка верхнего отступа у группы компонентов

Однако у данного решения есть один существенный минус — невозможность выгружать токены отступов в библиотеку фронтов, т.к. Figma не подтягивает свойства автолэйаутов. Потому, если есть цель держать токены отступов в формате где всё обновляется и подтягивается в библиотеку фронтов с обновлениями у дизайнеров — используйте спэйсеры или другие альтернативы. Но на наш взгляд, лучше задавать отступы в автолэйаутах, т.к. основные отступы не часто меняются. К тому же это позволяет соблюдать представленную структуру построения дизайн-системы.

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

В итоге, в блоке, между компонентами мы не выставляем отступы, т.к. они соблюдены в группах (1 блок в примере ниже). В свою очередь, отступы от групп компонентов, мы задаём в самом блоке (2 блок в примере ниже. 24px по бокам/сверху/снизу) тем самым управляя отступами блоков (в которых объединены группы компонентов) на уровне категории “блоки” в дизайн-системе.

Нулевые внутренние отступы из за применения групп компонентов (слева) и внутренние отступы блока от групп компонентов (справа)

Нулевые внутренние отступы из за применения групп компонентов (слева) и внутренние отступы блока от групп компонентов (справа)

4. Блоки
Блок — логически законченная, самостоятельно функционирующая часть системы. Является составной частью шаблона.

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

Примеры блоков

Примеры блоков

Объединяя их в единую композицию, мы получаем шаблон.

5. Шаблон:
Шаблон — это группа блоков (в случае с шаблоном модального окна это группа компонентов) с условными значениями, в дальнейшем заполняемый определённым содержанием (на проекте) в зависимости от контекста назначения шаблона.

Данная категория также включает в себя вариативность ситуативного выбора (настройки) под конкретную область применения, соответственно, каждый шаблон содержит в себе множество вариантов применения по направлениям разработки (web/mobile/ARM) продуктовым категориям направлений разработки (Продукт 1/Продукт 2) и т.д., который, в свою очередь настраивается через бар настройки в Figma: размеры под разрешения, область применения и т.д.

Например:
Ячейка значения в таблице, что не имеет смысла существования без таблицы. В данном случае, набор ячеек таблицы — это блок ячеек (в контексте шаблона, как конечной функционирующей структуры, блок ячеек ещё не является функционирующей структурой. В данном случае, блок ячеек является группой комбинированных компонентов). Сама таблица — блок таблицы в конечном варианте, т.к. будет дополнятся системными элементами вроде пагинации и прочих элементов управления, без которых набор ячеек — нефункционирующая часть, поскольку конечная форма определяется таблицей, как конечной, законченной логической формой с элементами управления, а не набором блоков.

Блок ячеек

Блок ячеек

Или компонент пагинации (системный элемент шаблона таблицы), который имеет поведенческие проявления, но вне контекста той же таблицы не имеет деятельностной активности. Словом — бессмысленен сам по себе.

Комбинированный компонент пагинации 

Комбинированный компонент пагинации 

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

Итоговый блок таблицы

Итоговый блок таблицы

В свою очередь, блок таблицы, дополняясь другими логически относящимися блоками — формируют шаблон.

Шаблон с блоком таблицы

Шаблон с блоком таблицы

Важно.
Имеет смыл под каждую сущность дизайн-системы — компонент, группу компонентов, блок, шаблон, создавать отдельную категорию, а относительно сущностей категории — создавать отдельный проект под каждую сущность категории дизайн-системы, чтобы количество компонентов не нагружало дизайн-систему, и чтобы можно было более обстоятельно вносить изменения и обновлять дизайн-систему, не перегружая её до того предела, где Figma «ляжет».

6. Страница
Страница — это заполненный контентом шаблон. Шаблоны которые берутся из библиотеки, заполняются контентом непосредственно на продукте (проекте). Редактирование контента на страницах производится на проекте (продукте) дизайнера. Далее страницы распространяются до этапа user-flow.

Шаблон + контент = страница

Шаблон + контент = страница

7. Сценарии (user-flow)
Сценарии (user-flow) — это визуальное представление пути пользователя, которое собирается из страниц (шаблонов заполненных обстоятельным контекстным контентом).

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

  • формы (часть общей дизайн-системы)

  • контент (часть локальной дизайн-системы на проекте)

Воздействуя на каждую по её смысловым назначениям — мы централизованно и обстоятельно управляем нужными нам направлениями.Сам же сценарий пути пользователя (User-flow) в виде заполненных контентом шаблонов — страниц, необходимо связать в интерактивных анимациях с помощью инструмента Figma — Prototype, тем самым, завершая общий процесс проектирования централизованной в управлении архитектуры дизайн-системы.

P.S.
Стоит отметить следующее обстоятельство. Данная структура, которая начинает свою работу с использования блоков (минуя компоненты и группы компонентов), более подходит для специалистов не являющихся дизайнерами. Она подойдёт для аналитиков, QA и т.д., для тех, кому не нужно понимать детальных обстоятельств настройки компонентов, блоков, локальной дизайн-системы и т.д.. Для них, данная фундаментальная и определённая в своей структуре часть дизайн-системы, будет являться неким конструктором, в котором выставлены все настройки (отступы, цвета, шрифты и тд). Той частью, которая точно является основной и неизменной, и при помощи которой можно выстраивать сценарии в макетах будучи не дизайнерами.

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

Дизайнеры, создавая экспериментальные сценарии на продуктах, используя компоненты, могут создавать локальные библиотеки, в которых будут настраивать компоненты в отступах между собой, помещая готовые конструкции в поле “placeholder”, которые, в свою очередь будут находиться в форме “рыбных” блоков и шаблонов, в которые можно засунуть мастер-компонент из объединенных компонентов (простых и комбинированных) и/или блоков компонентов. Данные композиции с “placeholder” будут находится в категориях “блоки” и “шаблоны” (шаблон модальных окон), как раз для того, чтобы опытным дизайнерам можно было экспериментировать с новыми внутренними формами, при этом используя общепринятые стили оболочек блоков и шаблонов (шаблон модальных окон).

Карта категорий:

Карта категорий

Карта категорий

Авторы статьи:
Бушков Филипп . Продуктовый дизайнер
Чмиленко Сергей . Продуктовый дизайнер

Особая благодарность:
Потапова Наталья . Проектировщик дизайн-систем
Ибрагимов Вагиз . Руководитель центра компетенций


ссылка на оригинал статьи https://habr.com/ru/post/724806/

Реверс инжиниринг для самых маленьких на практике

Скомпилированное приложение является «чёрным ящиком». Чтобы туда заглянуть, восстановить алгоритм работы применяется реверс‑инжиниринг. Это непростой навык с высоким порогом входа. В статье мы попробуем взять дизассемблер, несложную задачку и пойдём в бой. Материал будет полезен тем, кому хочется с чего-то начать и погрузиться в тему реверса.

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

Задача

В качестве примера для разбора мы взяли задачу с платформы Codeby Games из раздела Реверс-инжиниринг – файлы. Называется: «Шифрование или что-то в этом роде, не знаю». Чтобы получить доступ к платформе – нужно зарегистрироваться. К самой платформе мы не имеем никакого отношения.

Решением задачи является строка (флаг), которую будем добывать.
Скачиваем бинарный файл “task3.exe” из раздела “Файлы” и приступаем.

В статье будут применяться бесплатные инструменты:

Первичный запуск

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

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

Рекомендуем анализаторы
  • VirusTotal (Есть бесплатная версия)

  • Hybrid Analysis (Есть бесплатная версия)

  • AnyRun (Есть бесплатная версия)

  • PEStudio (Есть бесплатная версия)

  • Capa (Бесплатно)

Современное вредоносное ПО часто взаимодействует с сетью как для доставки очередной «стадии вредоносности», так и для распространения в локальной сети. Рекомендуем либо целиком отключать виртуальную машину от сети, либо, если для исследуемого приложения требуется подключение в интернет, использовать VPN изнутри виртуальной машины. Это не даст образцу распространиться в сети вашей хостовой машины.

Теперь, когда обо всем предупредили, можно приступать к работе.

Запускаем приложение!

Перед нами классическое консольное приложение, которое запрашивает строку:

Первичный запуск

Первичный запуск

Пробуем ввести флаг и убеждаемся, что удача не на нашей стороне:

Попытка ввода флага

Попытка ввода флага

Но, теперь мы знаем, что проверка флага каким-то образом связана со строками INPUT YOUR FLAG: и INCORRECT!!!.

Запускаем дизассемблер IDA и открываем исполняемый файл. IDA предлагает использовать оставленную автором отладочную информацию:

Наличие отладочной информации всегда сильно упрощает дальнейший анализ.

Немного об отладочной информации

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

Существует множество форматов отладочных символов. Чаще всего вам придется сталкиваться с этими:

  • PDB – Program Data Base, используется в продуктах, собранных с использованием компиляторов, разработанных компанией Microsoft.

  • DWARF (Debugging With Arbitrary Record Format), STABS (Symbol Table and String Table), COFF (Common Object File Format), ELF (Executable and Linkable Format) используются в основном в продуктах, собранных с использованием open-source компиляторов.

  • Mach-O – Mach Objecton macOS and iOS, используется только в продуктах, собранных различными компиляторами, для целевых ОС MacOs или iOS.

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

Не стоит пугаться такому списку, потому что суть одна и та же. Разберем чуть подробнее, что хранят в себе DWARF и PDB – самые часто используемые форматы.

Информация DWARF обычно хранится в отдельном файле, который затем связывается (линкуется) с исполняемым файлом.

Основная информация, хранящаяся в файле DWARF:

  • имена файлов исходного кода и номера строк

  • информацию о глобальных и локальных переменных и их типах

  • информацию о функциях и подфункциях

  • граф вызовов программы

Информация PDB обычно также хранится в отдельном файле, который затем связывается с исполняемым файлом.

Основная информация, хранящаяся в файле PDB:

  • информацию об именах и типах переменных

  • информацию о функциях

  • информацию о местоположении строк кода

  • информацию о стеке вызовов

О том, как управлять своей жизнью, и самому включать отладочную информацию на примере отладчика gdb и исполняемого ELF-файла, советуем прочитать эти две статьи:

В Visual Studio отладочная информация включается при наличии флага линковщика /DEBUG. Отключить можно в настройках проекта, поставив полю Linker -> Debugging -> Generate debug info значение NO.

Также в настройках линковщика можно отключить создание pdb файла, очистив поле Linker -> Debugging -> Generate Program Database File.

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

Копаем глубже:

Больше о том, как исследователи используют отладочную информацию можно почитать в статье Utilizing Debugging Information of Applications in Memory Forensics.

Крис Касперски в статье Защита игр от взлома рекомендовал никогда не оставлять отладочную информацию в скомпилированной программе.

Начинаем анализировать

Теперь, когда есть поверхностное знание, что такое отладочная информация начнем анализ. Для этого откроем IDA, с использованием отладочной информации. Перед нами функция main.

Функция main

Функция main

С отладочной информацией дизассемблер сразу понял, что перед нами не набор бессмысленных переменных. По модификаторам <Тип данных> ptrпонимаем, что flag, check и i это указатели на данные определенных типов.

Спускаемся ниже по коду к адресу 401598. По синтаксису команды mov [rbp+check+4], 7ABh видим, что в память по адресам check, check+4, check+8 и т.д. помещаются какие-то числа.

Копаем глубже

Больше о квадратных скобках в операндах команд и вообще о типах адресации можно почитать в статье Операнды в языке ассемблера.

Так как данные помещаются в check+N по регулярному смещению 4, делаем вывод, что check является указателем на массив. Этих команд 25 штук, поэтому можем предположить, что массив хранит в себе 25 элементов длиной 4 байта. Если это верно, то далее, когда массив будет использоваться в циклах, скорее всего операции будут проводиться с двадцатью пятью элементами.

Отвлечемся немного и ответим на пару вопросов.

А почему каждая строка начинается с “.text”?

Исполняемый файл имеет множество участков, называемых секциями. Они имеют различное назначение. Код в исполняемых файлах хранится в секции .text. У этой секции есть специальные права на исполнение. Чтобы в этом убедиться, откроем исполняемый файл в редакторе CFF Explorer.

Загрузив файл в CFF Explorer, видим вкладки в левой части рабочего пространства:

Сначала идут заголовки, потом таблицы: импорта, экспорта и TLS. Ниже различные модули для работы с исполняемыми файлами, включенные в CFF Explorer.

Информация о секциях хранится в заголовке Section Headers. Откроем его:

Копаем глубже

О структуре исполняемых файлов Windows подробне прочитать в статье Часть 1. Обзор PE формата и других статьях цикла.
Статьи покрывают: из чего состоит исполняемый файл Windows, как искать секции, что такое импортируемые и экспортируемые функции и так далее многое другое.
А выжимка есть на хабре PE (Portable Executable): На странных берегах. Для начала ее вполне хватит.

Нас интересует поле Characteristics. Здесь содержится информация о данных, хранящихся в секции. У секции .text Characteristics имеет значение 60500060. Старшая 6 говорит о том, что у секции есть характеристики:

  • IMAGE_SCN_MEM_READ и IMAGE_SCN_MEM_EXECUTE – секция может быть исполнена как код

  • IMAGE_SCN_MEM_READ – секция может быть прочитана

Копаем глубже

Все возможные характеристики можно посмотреть в документации Microsoft.

А почему после .text следует “040…”?

У исполняемого файла есть параметр ImageBase – смещение относительно адреса в памяти, куда была загружена программа. Обычно это 0x40000. Этот параметр хранится в заголовке Optional Header:

Так почему же main() не начинается с 400000? Потому что это адрес, где лежит весь исполняемый файл, в том числе заголовки. Заголовки находятся как раз над сегментом кода. Поэтому нас интересуют еще два поля Optional Header:

  • BaseOfCode – смещение относительно начала загруженного исполняемого файла (ImageBase). Здесь начинается сегмент кода text. В этой части размещается код начальной работы процесса: проверка сигнатуры исполняемого файла, получение аргументов main, StartupInfo для процесса и так далее.

  • AddressOfEntryPoint – смещение относительно ImageBase, откуда начинает исполняться программа. Здесь начинается функция mainCRTStartup, которая выполняется до человеческого main и является точкой входа библиотеки времени выполнения Си (C Runtime). Помимо прочего, здесь инициализируются статические элементы кода, а так же переменные argc, argv и envp. А еще, в этой функции помимо всего остального, инициализируются статические элементы (с пометкой static), если они используются в программе.

В нашем случае AddressOfEntryPoint равен 1510. Посмотрим на него в IDA. В IDA этот адрес будет равен ImageBase+AddressOfEntryPoint, т.е. 401510:

Вызов __tmainCRTStartup

Вызов __tmainCRTStartup

Здесь как раз видим вызов __tmainCRTStartup, находящийся выше AddressOfEntryPoint, в котором инициализируется StartupInfo.

Дважды нажав на эту функцию, перейдем внутрь нее и немного пролистаем до момента, пока не увидим вызов функции main (call main):

Вызов main

Вызов main

DspdsdsdsddsdЗдесь же видим, что в функцию main передаются привычные нам аргументы: argc, argv и envp. Это происходит всегда, даже когда разработчик приложения напрямую никак не взаимодействует с этими переменными в исходном коде.

Теперь вы знаете, как найти функцию main самостоятельно и как она вызывается на самом деле, а так же что означают числа рядом с .text в дизассемблере.

Если отладочной информации нет

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

Дизассемблер не заметил, что перед ним массив. Поэтому нам нужно методом пристального взгляда определить границы массива: переменную, начиная с которой переменные размещаются регулярно с каким-то фиксированным смещением. В нашем случае это var_70:

Ищем массив

Ищем массив

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

Далее выделим все необходимые переменные и с помощью горячей клавиши ‘*’ создадим массив:

Затем переименуем созданный массив с помощью горячей клавиши ‘N‘:

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

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

Наконец-то реверс

Вернемся к коду с отладочной информацией и продолжим.

Перейдем в раздел Strings (shift+F12) и видим уже знакомую строку, которая приглашает ввести флаг:

Строки

Строки

Дважды кликаем по ней в открывшемся окне и попадаем в секцию, где хранятся инициализированные данные приложения, предназначенные только для чтения – .rdata:

.rdata

.rdata

Чтобы посмотреть, где используется текущий адрес, жмем Ctrl+X и получаем список перекрестных ссылок. Механизм называется XRefs или перекрестные ссылки.

Переходим по единственной перекрестной ссылке:

Перекрестные ссылки

Перекрестные ссылки

Перейдя по ссылке, видим, что строка используется чуть ниже заполнения массива check (адрес 401640) в функции puts (вывод в стандартный поток вывода, в данном случае – консоль):

Ниже (адрес 40165D) замечаем вызов scanf (форматированный ввод из стандартного потока ввода) с параметрами %s и указателем flag. Значит, flag – строка, вводимая пользователем.

Спускаемся еще ниже и видим зануление значения по адресу i (адрес 401662) и на следующей строке безусловный переход на метку loc_4016b2. Метка – название для адреса в секции кода. Переходим по этой метке, дважды нажав на loc_4016b2.

Здесь (адрес 4016B2) мы видим сравнение i с числом 24 (18h), условный переход на метку loc_40166B, который сработает, если i будет меньше или равно 24. Если i будет больше 24, переход не произойдет и будет вызвана функция puts(“GOOD JOB, HACKER”):

Перейдя по метке loc_40166B, видим, что в этом участке кода производятся манипуляции с массивами flag (адрес 401670) и check (адрес 401691):

Первым делом значение i помещается в регистр eax и расширяется до rax командой cdqe (адреса 40166B и 40166E). Физически eax – младшая половина регистра rax:

Схема регистров

Схема регистров
Копаем глубже

Подробнее о регистрах можно узнать в статье Регистры процессора.

Строкой ниже (адрес 401670) в eax помещается значение, находящееся по адресу flag+raxc помощью команды movzx (rax в два раза больше eax, поэтому используется movzx, где zx означается, что данные, находящиеся в eaxдополняются нулями). Сопоставив эту строку с двумя предыдущими, понимаем, что в rax помещается i-ый элемент массива flag.

По адресу 401678 от регистра rax остается только младший байт, и расширяется до знакового четырех байтового числа командой movsx eax, al. Позже будет понятно зачем.

Далее для содержимого eax вызывается функция enc (строки 401682-401687). Параметр функции передается в регистре ecx. Кликнув дважды enc попадаем в ее тело:

Функция enc

Функция enc

Благодаря отладочным символам, видим, что функция возвращает тип int –четырёхбайтовое знаковое число и имеет соглашение о вызовах cdecl, а значит ее результат, возвращаемый в return будет храниться в регистре eax. При этом, параметр letter, передаваемый в функцию – четырёхбайтовое знаковое целое. Именно поэтому ранее мы видели расширение до четырёхбайтового целого.

Копаем глубже

На эту тему на хабре есть статья Соглашения о вызовах.

Расширение до четырех байт нужно для проверки модифицированного flag[i], имеющего однобайтовый тип с некоторым четырёхбайтовым элементом check.

В функции видим, что ecx сохраняется в переменную letter (адрес 401574), а затем, строкой ниже, значение переменной сохраняется в eax.

Следующей строкой со значением регистра eax производят операцию xor eax, 0BADh – побитового исключающего ИЛИ с числом 0x0BAD.

На этом тело функции заканчивается, значит, результатом ее исполнения является:

xor(flag[i], 0x0BAD)

Вернемся к коду, вызывающему эту функцию:

После вызова функции enc видим (адрес 401682), что результат ее выполнения, хранящийся в eax, увеличивается на 0x24 и для полученного значения снова вызывается функция enc.

В результате второго выполнения enc со значением flag[i]произведено действие:

xor(xor(flag[i], 0x0BAD+0x24),0x0BAD)

Разберемся далее построчно:

  1. 40168C: результат второго выполнения enc сохраняется в edx

  2. 40168E и 401691: значение i помещается в rax.

  3. 401693: в eax сохраняется значение check[rax*4], что означает сохранение в eax check[i*4]

  4. 401697-401699: полученное значение для i-го элемента флага сравнивается с check[i*4], и если значения равны, происходит переход на метку loc_4016AE

Переходим на метку и видим (адрес 4016AE) увеличение значения i на единицу:

После увеличения значения i снова попадаем на сравнение с 24. Так мы понимаем, что попали в цикл.

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

Цикл в IDA

Цикл в IDA

В IDA зеленые стрелки означают верный результат при сравнении, красные – неверный, синие – безусловный переход (может быть как признаком команды jmp, так и вовсе отсутствия команды j*).

Обратив внимание на условный переход jle на метку loc_40166B, понимаем – чтобы на нее не переходить и добиться вывода GOOD JOB, HACKER, необходимо пройти целиком весь цикл.

Алгоритм проверки флага

В результате обратной разработки функции проверки введенного флага, имеем уравнение, в котором неизвестным являетсяflag[i]:

(flag[i] ^ 0x0BAD + 36) ^ 0x0BAD = check[i]

Так как xor является обратимой функцией, получим flag[i] из check[i]:

int flag[i] = (check[i] ^ 0x0BAD – 36) ^ 0x0BAD

Далее остается только взять младший байт от flag[i]. Возьмем значения элементов массива из дизассемблированного кода и напишем простую программу на C:

int main() {  int check[25]; char flag[25]; int i; check[0] = 1983; check[1] = 1963; check[2] = 1952; check[3] = 1953; check[4] = 1982; check[5] = 1973; check[6] = 87; check[7] = 70; check[8] = 1969; check[9] = 1967; check[10] = 80; check[11] = 1979; check[12] = 93; check[13] = 1979; check[14] = 1967; check[15] = 109; check[16] = 73; check[17] = 1964; check[18] = 72; check[19] = 111; check[20] = 1979; check[21] = 84; check[22] = 108; check[23] = 1966; check[24] = 89; for (int i = 0; i <= 24; i++) { flag[i] = (char)(((check[i] ^ 0x0BAD) - 36) ^ 0x0BAD); } printf(flag); }

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

В заключение скажем, что можно было так не страдать, сразу нажать волшебную кнопку F5, декомпилировать код и получить что-то вроде этого:

Однако, понимания в реверсе это не добавит. IDA не всегда декомпилирует во что-то читабельное (особенно при отсутствии отладочной информации), и все равно нужно сидеть над ассемблером и изучать, кто на ком стоял.

Заключение

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


ссылка на оригинал статьи https://habr.com/ru/post/724808/