...

суббота, 6 июня 2015 г.

Разработка OS на Go+asm Part 0x00

Доброго времени суток %username%.

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

Как некоторым известно, я очень люблю язык Go ну, и решил попробовать написать на нем. Что из этого получилось — под хабракатом.


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

Для начала напишем код первоначальной загрузки (файл multiboot.s)

MBOOT_PAGE_ALIGN    equ 1<<0
MBOOT_MEM_INFO      equ 1<<1
MBOOT_HEADER_MAGIC  equ 0x1BADB002
MBOOT_HEADER_FLAGS  equ MBOOT_PAGE_ALIGN | MBOOT_MEM_INFO
MBOOT_CHECKSUM      equ -(MBOOT_HEADER_MAGIC + MBOOT_HEADER_FLAGS)

[BITS 32]

[GLOBAL mboot]
[EXTERN code]
[EXTERN bss]
[EXTERN end]

mboot:
  dd    MBOOT_HEADER_MAGIC
  dd    MBOOT_HEADER_FLAGS
  dd    MBOOT_CHECKSUM
  dd    mboot
  dd    code
  dd    bss
  dd    end
  dd    start

[GLOBAL start]
extern go.kernel.Load ;Указываем на то, что у нас есть внешняя функция на Go

start:
  push  ebx
  cli
  call  go.kernel.Load ;Вызываем внешнюю функцию, которая содержит основной код ядра
  jmp   $

теперь создадим файл kernel.go следующего содержания:

package kernel

function Load(){
//Как видим наше ядро пока ничего не делает
}

Создадим файл link.ld

ENTRY(start)
SECTIONS
{

    .text 0x100000 :
    {
        code = .; _code = .; __code = .;
        *(.text)
        . = ALIGN(4096);
    }

    .data :
    {
        data = .; _data = .; __data = .;
        *(.data)
        *(.rodata)
        . = ALIGN(4096);
    }

    .bss :
    {
        bss = .; _bss = .; __bss = .;
        *(.bss)
        . = ALIGN(4096);
    }

    end = .; _end = .; __end = .;
}

и Makefile

SOURCES=multiboot.o kernel.go.o

GOFLAGS= -nostdlib -nostdinc -fno-stack-protector -fno-split-stack -static -m32 -g -I.
GO=gccgo
ASFLAGS= -felf
NASM= nasm $(ASFLAGS)
OBJCOPY=objcopy

LDFLAGS=-T link.ld -m elf_i386
 

all: $(SOURCES) link

clean: 
        rm *.o  kernel 

link:
        ld $(LDFLAGS) -o kernel $(SOURCES)


%.go.o: %.go
        $(GO)   $(GOFLAGS) -o $@ -c $<

%.o: %.s
        $(NASM) $<

Теперь, выполнив make в дирректории проекта вы получите на выходе файл kernel, который можно загрузить с помощью qemu:

qemu-system-i386 -kernel ./kernel

Ядро успешно загрузится и ничего не будет делать )

Настало время поздороваться с миром.

Для начала добавим в multiboot.s следующие строки:

global __go_runtime_error
global __go_register_gc_roots
global __unsafe_get_addr

__unsafe_get_addr:
  push ebp
  mov ebp, esp
  mov eax, [ebp+8]
  mov esp, ebp
  pop ebp
  ret

__go_register_gc_roots:
__go_runtime_error:
  ret

функция __unsafe_get_addr нужна для того, что бы мы могли конвертировать uint32 в указатели внутри Go

Другие две функции — просто затычки для компилятора

Теперь создадим файл screen.go

package screen

var (
        frameBuffer      *[totalMax]uint16 //Старшие 8 бит - символ, младшие - его атрибуты
        cursorX, cursorY uint8
)

const (
        frameBufferAddr = 0xB8000
        maxX            = 80
        maxY            = 25
        totalMax        = maxX * maxY
        whiteOnBlack    = 0x07
)
//Ниже мы создаем обертку для Go над нашей функцией __unsafe_get_addr
//extern __unsafe_get_addr
func getAddr(addr uint32) *[totalMax]uint16

func Init() {
        cursorX = 0
        cursorY = 0
        frameBuffer = getAddr(frameBufferAddr) //Получаем доступ к видеобуферу
}

//Очистка экрана, просто заполняем весь видеобуфер нулями
func Clear() {
        for i := 0; i < totalMax; i++ {
                frameBuffer[i] = 0
        }
        cursorX = 0
        cursorY = 0
}

//Меняем позицию курсора
func SetCursor(x, y uint8) {
        cursorX = x
        cursorY = y
}

//Скроллим экран если он заполнен
func scroll() {
        if cursorY >= maxY { 
                for i := 0; i < 24*maxX; i++ {
                        frameBuffer[i] = frameBuffer[i+80] //Смещаем все строки на одну вверх
                }
                for i := 24 * 80; i < totalMax; i++ {
                        //Очищаем нижнюю строку
                        frameBuffer[i] = 0x20 | (((0 << 4) | (15 & 0x0F)) << 8)
                        frameBuffer[i] = 0
                }
                cursorY = 24
                cursorX = 0
        }
}

//Вывод символов
func putChar(c byte) {
        switch c {
        case 0x08: //backspace
                if cursorX > 0 {
                        cursorX--
                }
        case 0x09: //tab
                cursorX = (cursorX + 8) & (8 - 1)
        case '\r': //return
                cursorX = 0
        case '\n': //new line
                cursorX = 0
                cursorY++
        default: 
                if c >= 0x20 { //Все печатные символы
                        frameBuffer[cursorY*80+cursorX] = uint16(c) | (((0 << 4) | (15 & 0x0F)) << 8)
                        cursorX++
                }
        }
        if cursorX >= 80 { //Если надо перемещаем курсор
                cursorX = 0
                cursorY++
        }
        scroll()
}

//Выводим строку
func PrintStr(s string) {
        for i := 0; i < len(s); i++ {
                putChar(s[i])
        }
}

Теперь надо подключить наш модуль screen к ядру — в kernel.go добавляем import «screen», там же, в функци Load() пишем:

  screen.Init()
        screen.Clear()
        screen.PrintStr("Hello Habrahar!")

Теперь надо указать компилятору как все это дело собирать нам понадобится добавить в Makefile следующие строки:

%.gox: %.go.o
                $(OBJCOPY) -j .go_export $< $@


И там же, в переменную SOURCES между multiboot.o и kernel.go.o добавить screen.go.o и screen.gox

После проведения всех манипуляций вызываем команду make и запускаем qemu с нашим ядром. Дожидаемся загрузки и радуемся

P.S. Прошу простить меня за опечатки, если они есть. Обязуюсь исправить.
P.P.S. На днях код будет выложен на github

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

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Vim по полной: Введение

Привет, хабраюзер!

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

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

  1. Введение (vim_lib)
  2. Менеджер плагинов без фатальных недостатков (vim_lib, vim_plugmanager)
  3. Уровень проекта и файловая система (vim_prj, nerdtree)
  4. Snippets и шаблоны файлов (UltiSnips, vim_template)
  5. Компиляция и выполнение чего угодно (vim_start)
  6. Работа с Git (vim_git)
  7. Деплой (vim_deploy)
  8. Тестирование с помощью xUnit (vim_unittest)
  9. Библиотека, на которой все держится (vim_lib)
  10. Другие полезные плагины

Хочется сразу заметить, что я не преследую цель «посадить как можно больше людей на иглу Vim», так как статья больше расчитана на опытных пользователей, нежели на новичков.
Основная проблема редактора (если не считать VimLanguage), это огромное количество разнообразных плагинов на все случаи жизни. Казалось бы, что в этом плохого? Дело в том, что плагины пишутся огромным сообществом, от чего они крайне плохо стыкуются между собой. Часто можно столкнуться с тем, что один из твоих плагинов частично умеет то же, что и два других, просто потому, что автор этого плагина решил добавить в него побольше «плюшек». Если заглянуть в код большинства плагинов, можно встретить множество дублирующихся от плагина к плагину решений и алгоритмов, которые по хорошему должны быть вынесены либо в VimLanguage, либо в некоторую единую библиотеку. К сожалению некоторые авторы плагинов пытаются реализовывать эти библиотеки, но после нескольких простеньких плагинов, созданных на базе этих библиотек, они прекращают поддержку и помимо десятка не слишком различающихся плагинов, вы должны установить парочку библиотек.

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


Свое движение к «чистому и прекрасному» я начал с написания библиотеки. Переписывал ее я целых три раза, ибо мне нужно было хорошенько разобраться во всех тонкостях VimLanguage, его возможностях и ограничениях, а их, я вам скажу, там множество! В результате получилась библиотека vim_lib, которая меня полностью удовлетворяет.

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

Вот вам небольшой пример. Предположим, мы работаем с web-проектом используя PHP и JavaScript, а в свободное время пишем книгу с помощью LaTeX. Что вам могу предложить я и Vim:

  • Все общие настройки выносятся в каталог ~/.vim/, после чего они будут доступны во всех проектах
  • Настройки для web-проекта мы положим в каталог webProject/.vim/, а для работы с книгой в bookProject/.vim/
  • Каждый раз, открывая окно редактора Vim, мы будем видеть
    список недавних проектов
    а перейдя в проект, он откроется в месте его последнего редактирования с восстановлением всех окон
  • Чтобы каждый раз не создавать файлы в чистого листа (на пример Unit-тесты, классы Mapper для бизнес-логики или главы книги), мы создадим
    готовые шаблоны файлов
    в каждом проекте (на пример для Unit-тестов webProject/.vim/templates/test/___Test.php)
  • Кто захочет постоянно набирать
    foreach(...)
    
    Пусть это сделает за нас Vim. Создадим несколько snippets на все случаи жизни
  • Для работы с LaTeX установим прямо в проект bookProject специфичные для LaTeX плагины
  • Но мы не одиноки, потому поделимся с нашими соратниками своими наработками с помощью Git, а для этого у нас есть
    удобный интерфейс прямо в Vim
  • Закончили очередную главу и хотите посмотреть на нее в pdf читалке? Не вопрос! Нажимайте F9 и книга будет скомпилирована и показана вам незамедлительно. Есть ли смысл упоменать о других скриптах, которые можно выполнить столь же просто?
  • Решили запустить тестирование конкретного класса? Не вопрос, и для этого найдется плагин
  • Пришло время слить изменения в продакшн? Нажимаем одну кнопку и они уже там

Опытные пользователи Vim скажут: «Пфф… Для всего этого есть куча готовых плагинов. Велосипед!» — но вы просто не знаете на что способны предлагаемые мной решения. Мало того, что они легко стыкуются друг с другом, так они еще и более функциональные по сравнению с аналогами среди плагинов Vim! В этом вы сможете убедится в следующих статьях цикла, просто наберитесь терпения.


Практически все свои плагины я сопровождаю подробной документацией, потому попробовать вы их можете уже сейчас. Вот перечень реализованных на сегодняшний день решений:
  • vim_lib — базовая библиотека, без которой у вас ничего работать не будет. Она так же определяет порядок загрузки редактора
  • vim_plugmanager — моя реализация пакетного менеджера для Vim без «фатальных недостатков». Особенностью этого решения является возможность установки плагина как в пользовательский каталог, так и в каталог проекта без необходимости изменять конфигурацию плагина
  • vim_prj — очень полезный плагин, позволяющий сохранять и восстанавливать состояние проекта при его открытии и закрытии
  • vim_start — плагин реализующий стартовое меню с возможностью быстрого перехода к недавним проектам
  • vim_git — очень мощный плагин для работы с Git прямо из Vim
  • vim_grep — поиск в проекте
  • vim_deploy — плагин для работы с любыми системами деплоя (за счет адаптеров к ним). На сегодня реализовано только два адаптера: shipit и gradle
  • vim_unittest — плагин для работы с любыми системами xUnit (за счет адаптеров к ним). На сегодня реализован только адаптер для phpunit
  • vim_template — очень полезный плагин, позволяющий гибко настраивать шаблоны для новых файлов
  • vim_write — автоматическое сохранение проекта
  • vim_winmanager — работа с окнами Vim

Для установки создайте каталог ~/.vim/bundle (можно использовать любое имя) и скопируйте туда vim_lib. После добавьте в ваш .vimrc следующую запись:

filetype off 
set rtp=~/.vim/bundle/vim_lib
call vim_lib#sys#Autoload#init('~/.vim', 'bundle') " Адрес до вашего ~/.vim/bundle

Plugin 'vim_lib'
" Другие плагины

filetype indent plugin on

В общем, все как всегда.


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

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

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Нативная реализация OmniDirectional теней в DirectX11

image

Привет. Продолжая рассказывать про различные технологии из графического геймдева — хотел бы рассказать о том, как в DirectX 11 удобно работать с тенями. Расскажу о создании Point-источника света с полным использованием инструментов GAPI DirectX11, затрону такие понятия, как: Hardware Depth Bias, GS Cubemap Render, Native Shadow Map Depth, Hardware PCF.
Исходя из легкого серфинга по интернету – я пришел к выводу, что большинство статей о тенях в DX11 неверны, реализованы не совсем красиво или с использованием устаревших подходов. В статье постараюсь сравнить реализацию теней в DirectX 9 и DirectX 11. Все ниже описанное так же справедливо и для OpenGL.

Введение


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

Еще в далеком 1978-ом году, Ланс Уильямс представил концепт метода создания теней, который назывался Projective shadowing (Shadow mapping) и на состояние 2015-ого года лучше технологий теней доступных в продакшене нет. Да, существует масса модификаций Projective shadowing, но они все, так или иначе основаны на последнем. Так в чем же суть этого метода и как можно получить тени от геометрии любой сложности, да еще и в реалтайме? Суть в том, что из позиции источника света рендерится вся сцена без текстур и запоминается только расстояние от источника света до фрагмента сцены, именуют эту вещь просто Shadow Map (карта теней). Это делается все до основного рендера сцены. И дальше, в самом просто случае – рисуется основная сцена и для каждого фрагмента (пикселя) этой сцены определяется – видит ли источник этот фрагмент (источник света можно назвать камерой) или нет. Если видит – свет попадает на фрагмент, если не видит – не попадает, все просто. Ну и скажу еще по поводу математики этого процесса, практически все реалтаймовые технологии рендеринга завязаны на матрицах, о них я писал чуть-чуть тут. Определение “видимости” фрагмента сцены источником света заключается в следующем: выполняется преобразование позиции фрагмента из пространства основной камеры (матрица вида и проекции основной камеры) в пространство источника света: как результат получается две сравнимые величины. Первая – расстояние из центра источника света до фрагмента сцены, а вторая – та самая глубина (расстояние) из Shadow map, который был создан ранее. Сравнивая их – можно определить, в тени ли фрагмент или нет.
Вот так выглядит самая простая реализация теней. Так же хочу отметить, что источники света бывают разными, следовательно, тени тоже будут разными. Выделяют четыре основных типа источника света:

  • Spot Light (он же Projection Light) — его лучше всего представить как обычный проектор, который имеет угол конуса света и направлен в определенное место. Матрицей такого источника света является Perspective Martix.
  • Point Light (он же Omnidirectional Light) – всенаправленный источник света, проще представить как точку (отсюда и Point), которая во всех направлениях испускает свет. Матрицей такого источника является Perspective Matrix с углом обзора (Field of View, FOV) в 90 градусов, при этом он имеет шесть видовых матриц, которые направленны каждый в свою сторону (стороны куба).
  • Directional Light (он же Sun Light) – бесконечно удаленный источник света, солнце на земле к примеру. Матрицей такого источника света является Orthographic Matrix.
  • Ambient Light – особый источник света, который не имеет позиции. Несет в себе информацию о равномерном свете полученным в результате переотражений света от других источников. В последние время сходит на нет и заменяется продвинутыми алгоритмами Global Illumination.

В теории все звучит хорошо и согласованно, но на практике появляются некоторые проблемы.
Первая и самая главная проблема – это дискретность карты теней. В памяти GPU они хранятся как текстуры особого формата, имеющие конечный размер (в играх, параметр “качество теней” зачастую связан с размером карты теней). Представить эту проблему можно, если расположить маленький фрагмент геометрии прям перед источником света и отбросить тень на большое полотно. Из-за этого расстояния между концами луча и его началом становятся несопоставимы (ведь карты теней конечного размера). Множеству разных позиций на полотне будет соответствовать один и тот же пиксель на карте теней, это приводит к такому эффекту, как Aliasing (тень выглядит ступенчато):
image

С этой проблемой существует множество средств борьбы, от нестандартных матриц проекции (например Trapezoidal Shadow Map, Geometry Pitch Shadow Map) до создания большого количества карт теней (4e: Cascaded Shadow Map). Но все эти алгоритмы являются довольно узкими для применения и не являются универсальными. Самый распространённый способ избавиться от сильного алиасинга – это Percentage Closer Filtering, который заключается в том, чтобы сделать несколько выборок с определенным константным смещением и интерполировать данные, но о нем подробнее позже.

В этой статье я рассмотрю один из самых сложных источников света – Omnidirectional Light. Т.к. свет он излучает во все стороны и придется делать необычную теневую карту – Cube Shadow Map.
Начнем. Что необходимо реализовать для точечного источника света?

  • Рендер сцены в специальную теневую карту – Cube Shadow Map
  • Шейдер для просчета освещения по какой-нибудь модели, например Ламберта
  • Фильтрация тени средствами DirectX11

Рендер теневой карты


В эпоху DirectX9 честные point-light с тенями мало кто делал, но если и делали – то с большой тратой ресурсов. Для того, чтобы посчитать тень для всенаправленного источника света — необходимо прорендрить мир вокруг источника света не один раз, тут два варианта – либо Dual Paraboloid Shadow Mapping (два раза), либо Cube Shadow Mapping (шесть раз). Остановимся на втором, т.к. он наиболее традиционен для point-light. Итак, в эпоху DirectX9 рендерили мир в специальную кубическую текстуру, которая в себе содержала шесть двухмерных текстур, каждый раз переключали активную сторону (текстуру) и мир рисовался заново. К сожалению – многие сейчас продолжают делать так же, даже на DirectX11. Вторая проблема была такова, что в DirectX9 нельзя было работать с хардварной глубиной из шейдера и приходилось писать глубину для дальнейшего использования вручную (зачастую в линейном виде).
В DirectX 9 Cube Shadow Map работал следующим образом:
  • Мир рендерился шесть раз – для каждой стороны отдельно
  • Мир рендерился в Render Target формата R32_Float и был обычной текстурой
  • В подавляющем большинстве глубина фрагментов записывалась в линейном формате
  • Тени вручную фильтровались при накладывании

Отдельно хочу сказать, почему в линейном формате? Дело в том, что в проверке принадлежности тени к текущему фрагменту нужно было сравнить две величины: первая – глубина записанная в теневой карте, а вторая – текущая глубина фрагмента в пространстве источника света. Если в случае Spot/Directional было все просто, мы брали точку и делали репроекцию этой точки в пространство источника света (путем умножения на матрицу вида/проекции источника света) и сравнивали эти две глубины. То в случае с Point-light все становится сложнее, у нас есть шесть разных видовых матриц, а это значит, что мы сначала должны определить – в каком фейсе лежит фрагмент и сделать репроекцию с помощью конкретной матрицы фейса. Это означало, что мы должны были использовать Dynamic Flow Control в шейдере, а это довольно тяжело для GPU. Поэтому поступали проще: хранили глубину в линейном формате (в теневой карте хранилось расстояние от источника света до фрагмента) и сравнивали с линейной глубиной при наложении. При таком рендере использовался хардвардный буфер глубины и Render Target, куда записывалась линейная глубина.

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

Как же работают такие же тени в DirectX11?

  • Мир рендериться один раз – геометрический шейдер автоматически выбирает нужную сторону для записи
  • Подход больше не использует Render Target и рендерит только нативную глубину в хардварный буфер
  • Глубина фрагментов имеет стандартный вид: p.z / p.w
  • Возможность использовать Hardware PCF

Практика

Теперь, рассмотрим как все это выглядит в реализации. Самое первое, что нужно для реализации Cube Shadows Mapping – это матрицы вида и проекции:

_projection = Matrix.PerspectiveFovRH(
                    MathUtil.DegreesToRadians(90.0f),
                    1.0f,
                    0.01f,
                    this.Transform.Scale.X);

Матрица проекции всегда имеет угол обзора в 90 градусов, aspect ratio в единицу (куб все же есть куб), far plane равен радиусу источника света.
Матриц вида у этого источника света – шесть:
            _view[0] = Matrix.LookAtRH(position, position + Vector3.Right, Vector3.Up);
            _view[1] = Matrix.LookAtRH(position, position + Vector3.Left, Vector3.Up);
            _view[2] = Matrix.LookAtRH(position, position + Vector3.Up, Vector3.BackwardRH);
            _view[3] = Matrix.LookAtRH(position, position + Vector3.Down, Vector3.ForwardRH);
            _view[4] = Matrix.LookAtRH(position, position + Vector3.BackwardLH, Vector3.Up);
            _view[5] = Matrix.LookAtRH(position, position + Vector3.ForwardLH, Vector3.Up);

Каждая видовая матрица описывает свой фейс, в DirectX11 порядок CubeTexture таков: Right, Left, Up, Down, Front, Back.
Далее идет особое описание Hardware Depth Buffer:
TextureDescription cubeDepthDescription = new TextureDescription()
                                {
                                        ArraySize = 6,
                                        BindFlags = BindFlags.ShaderResource | BindFlags.DepthStencil,
                                        CpuAccessFlags = CpuAccessFlags.None,
                                        Depth = 1,
                                        Dimension = TextureDimension.TextureCube,
                                        Format = SharpDX.DXGI.Format.R32_Typeless,
                                        Height = CommonLight.SHADOW_CUBE_MAP_SIZE,
                                        MipLevels = 1,
                                        OptionFlags = ResourceOptionFlags.TextureCube,
SampleDescription = new SharpDX.DXGI.SampleDescription(1, 0),
                                        Usage = ResourceUsage.Default,
                                        Width = CommonLight.SHADOW_CUBE_MAP_SIZE
                                };

Bing флаги – это шейдерный ресурс и то, что наша текстура является буфером глубины.
Так же важно отменить, что формат установлен в R32_Typeless, это обязательное требование при чтении хардварной глубины.
Из-за того, что мы не используем рендер в текстуру – нам достаточно заполнить хардварный буфер глубины данными:
_graphics.SetViewport(0f, 0f, (float)CommonLight.SHADOW_CUBE_MAP_SIZE, (float)CommonLight.SHADOW_CUBE_MAP_SIZE);
_graphics.SetRenderTargets((DepthStencilBuffer)light.ShadowMap);
_graphics.Clear((DepthStencilBuffer)light.ShadowMap, SharpDX.Direct3D11.DepthStencilClearFlags.Depth, 1f, 0);

_cubemapDepthResolver.Parameters["View"].SetValue(((OmnidirectionalLight)light).GetCubemapView());
_cubemapDepthResolver.Parameters["Projection"].SetValue(((OmnidirectionalLight)light).GetCubemapProjection());

scene.RenderScene(gameTime, _cubemapDepthResolver, false, 0);

Устанавливаем размер вьюпорта, устанавливаем буфер глубины, эффект и рендерим нашу сцену.
Стандартный шейдер нужен только один – вершинный, пиксельный отсутствует потому, что мы, опять же, не используем Render To Texture:
VertexOutput DefaultVS(VertexInput input)
{
        VertexOutput output = (VertexOutput)0;

        float4 worldPosition = mul(input.Position, World);
        output.Position = worldPosition;

        return output;
}

Вот только тут появляется еще один шейдер – геометрический, он то и будет выбирать нужный фейс для записи глубины:
[maxvertexcount(18)]
void DefaultGS( triangle VertexOutput input[3], inout TriangleStream<GeometryOutput> CubeMapStream )
{
        [unroll]
    for( int f = 0; f < 6; ++f )
    {
                {
                GeometryOutput output = (GeometryOutput)0;

                        output.RTIndex = f;

                        [unroll]
                        for( int v = 0; v < 3; ++v )
                        {
                                float4 worldPosition = input[v].Position;
                                float4 viewPosition = mul(worldPosition, View[f]);
                                output.Position = mul(viewPosition, Projection);

                                CubeMapStream.Append( output );
                        }

                        CubeMapStream.RestartStrip();
        }
    }
}

Его задача состоит в том, чтобы на вход получать треугольник, а в ответ эмитить шесть, но каждый будет находится в своем фейсе (параметр RTIndex). Вот так выглядят структуры:
cbuffer Params : register(b0)
{
        float4x4 World;
        float4x4 View[6];
        float4x4 Projection;
};

struct VertexInput
{
        float4 Position : SV_POSITION;
        //uint InstanceID : SV_InstanceID;
};

struct VertexOutput
{
        float4 Position : SV_POSITION;   
        //uint InstanceID : SV_InstanceID;
};

struct GeometryOutput
{
        float4 Position : SV_POSITION;   
        uint RTIndex : SV_RenderTargetArrayIndex;
};

Человек, работавший с множественным рендером одной и той же модели может заменить, что взамен эмита новой геометрии – можно использовать инстансинг, выбирая нужный RTIndex исходя из InstanceID. Да, можно, но я получил заметный проигрыш в перфомансе. В подробности, почему так получилось – не вдавался. Оказалось куда проще заимитить новые треугольники, чем использовать полученные от инстансинга.
После этого процесса – мы можем получить хардварную кубическую глубину. При этом рендер этой геометрии был сделан в один проход.
Следующий этап – это накладывание тени, в моем примере используется Deferred Shading, но все справедливо и для Forward-рендеринга. Теперь опять о проблемах: мы должны перевести расстояние от источника света до фрагмента в пространство источника света (кубического буфера глубины), но просто так это сделать нельзя, ведь для этого необходимо знать, какую из шести видовых матриц использовать. Dynamic Flow Control использовать не хочется, поэтому можно прийти к интересному хаку, который основан на том, что все видовые матрицы одинаковые и имеют FOV в 90 градусов:

float _vectorToDepth(float3 vec, float n, float f)
{
    float3 AbsVec = abs(vec);
    float LocalZcomp = max(AbsVec.x, max(AbsVec.y, AbsVec.z));

    float NormZComp = (f+n) / (f-n) - (2*f*n)/(f-n)/LocalZcomp;
    return (NormZComp + 1.0) * 0.5;
}

Таким способом мы можем определить глубину в пространстве источника света определенного вектора.
Теперь, мы можем прочитать глубину из теневой карты по трехмерному вектору [FragmentPosition-LightPosition] и по этому же вектору получить глубину в пространстве источника света, сравнить их и определить: фрагмент в тени или нет.
После прохода шейдеров получения тени и рендеринга Light-карты — получаем тень с сильным алиасингом. Для этого тень хорошо бы обработать фильтром и на помощь приходит возможность DirectX11 – Hardware PCF, эта возможность реализуется с помощью использования специальных comprasion-сэмплеров:
SamplerComparisonState LightCubeShadowComparsionSampler : register(s0);

Описывается он следующим образом:
var dms4 = SharpDX.Direct3D11.SamplerStateDescription.Default();
dms4.AddressU = SharpDX.Direct3D11.TextureAddressMode.Clamp;
dms4.AddressV = SharpDX.Direct3D11.TextureAddressMode.Clamp;
dms4.Filter = SharpDX.Direct3D11.Filter.ComparisonMinMagMipLinear;
dms4.ComparisonFunction = SharpDX.Direct3D11.Comparison.Less;

И делается выборка так:
LightCubeShadowMap.SampleCmpLevelZero(LightCubeShadowComparsionSampler, lightVector, obtainedDepth).r

Где obtainedDepth – глубина, которая получена из функции _vectorToDepth.
На выходе получается сглаженный результат сравнения глубин (если фильтр у сэмплера был Linear), что эквивалентно 2x2 Bilinear PCF:
image

Так же, можно сделать еще дополнительный 3x3 HPCF и получить такой результат:
image

Совсем забыл упомянуть еще об одной проблеме: как говорилось ранее – буфер глубины дискретен, это означает, что любая поверхность отраженная в этом буфере выглядит прерывисто (из-за ограниченной точности), вот так:
image

Поверхность начинает отбрасывать тень сама на себя, создавая неверную тень:
image

Эта проблема решается, если при сравнении сдвигать одну из глубин на некоторое маленькое значение (bias), чтобы хоть как-нибудь нивелировать эту проблему. Обычно делают что-то вроде: cD + 0.0001 < sD при проверке. Такой способ вреден, потому, что сдвигая таким способом – мы очень легко получаем эффект Питера Пэна:

Питер пэн
image

Для эффективного решения проблемы в DirectX11 есть стандартные средства, устанавливаются эти bias значения в Rasterizer State, параметры DepthBias и SlopeScaledDepthBias.
Вот таким нехитрым способом реализуются сглаженные point-light тени с использованием возможностей DirectX11.

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

DeferredShading.fx
#include "..//pp_GBuffer.fxh"
#include "Lights.fxh"

float4 PointLightPS(float2 UV : TEXCOORD) : SV_TARGET
{
        SurfaceData surfaceData = GetSurfaceData(UV);

        float3 texelPosition = GetPosition(UV);
        float3 texelNormal = surfaceData.Normal;

        float3 vL = texelPosition - LightPosition;
        float3 L = normalize(vL);

        float3 lightColor = _calculationLight(texelNormal, L);

        float3 lightCookie = float3(1, 1, 1);
        
        if(IsLightCookie)
        {
                float3 rL = mul(float4(L, 1), LightRotation).xyz;
                lightCookie = LightCubeCookie.Sample(LightCubeCookieSampler, float3(rL.xy, -rL.z) ).rgb;
        }

        float shadowed = 1;

        if(IsLightShadow)
                shadowed = _sampleCubeShadowHPCF(L, vL);

        //if(IsLightShadow)
        //      shadowed = _sampleCubeShadowPCFSwizzle3x3(L, vL);

        float atten = _calcAtten(vL);

        return float4(lightColor * lightCookie * shadowed * atten, 1);
}

technique PointLightTechnique
{
        pass 
        {
                Profile = 10.0;
                PixelShader = PointLightPS;
        }
}


Lights.fxh
cbuffer LightSource : register(b1)
{
        float3 LightPosition;
        float LightRadius;
        float4 LightColor;
        float4x4 LightRotation;

        float2 LightNearFar;
        const bool IsLightCookie;
        const bool IsLightShadow;
};

TextureCube<float4> LightCubeCookie : register(t3);
SamplerState LightCubeCookieSampler : register(s1);
TextureCube<float> LightCubeShadowMap : register(t4);

SamplerComparisonState LightCubeShadowComparsionSampler : register(s2);
SamplerState LightCubeShadowPointSampler : register(s3);

float _calcAtten(float3 vL)
{
        float3 lVec = vL / LightRadius;
        return max(0.0, 1.0 - dot(lVec,lVec));
}

float3 _calculationLight(float3 N, float3 L)
{
        return LightColor.xyz * saturate(dot(N, -L)) * LightColor.w;
}

float _vectorToDepth(float3 vec, float n, float f)
{
    float3 AbsVec = abs(vec);
    float LocalZcomp = max(AbsVec.x, max(AbsVec.y, AbsVec.z));

    float NormZComp = (f+n) / (f-n) - (2*f*n)/(f-n)/LocalZcomp;
    return (NormZComp + 1.0) * 0.5;
}

float _sampleCubeShadowHPCF(float3 L, float3 vL)
{
        float sD = _vectorToDepth(vL, LightNearFar.x, LightNearFar.y);

        
        return LightCubeShadowMap.SampleCmpLevelZero(LightCubeShadowComparsionSampler, float3(L.xy, -L.z), sD).r;
}

float _sampleCubeShadowPCFSwizzle3x3(float3 L, float3 vL)
{
        float sD = _vectorToDepth(vL, LightNearFar.x, LightNearFar.y);

        float3 forward = float3(L.xy, -L.z);
        float3 right = float3( forward.z, -forward.x, forward.y );
        right -= forward * dot( right, forward );
        right = normalize(right);
        float3 up = cross(right, forward );

        float tapoffset = (1.0f / 512.0f);

        right *= tapoffset;
        up *= tapoffset;

        float3 v0;
        v0.x = LightCubeShadowMap.SampleCmpLevelZero(LightCubeShadowComparsionSampler, forward - right - up, sD).r;
        v0.y = LightCubeShadowMap.SampleCmpLevelZero(LightCubeShadowComparsionSampler, forward - up, sD).r;
        v0.z = LightCubeShadowMap.SampleCmpLevelZero(LightCubeShadowComparsionSampler, forward + right - up, sD).r;
        
        float3 v1;
        v1.x = LightCubeShadowMap.SampleCmpLevelZero(LightCubeShadowComparsionSampler, forward - right, sD).r;
        v1.y = LightCubeShadowMap.SampleCmpLevelZero(LightCubeShadowComparsionSampler, forward, sD).r;
        v1.z = LightCubeShadowMap.SampleCmpLevelZero(LightCubeShadowComparsionSampler, forward + right, sD).r;

        float3 v2;
        v2.x = LightCubeShadowMap.SampleCmpLevelZero(LightCubeShadowComparsionSampler, forward - right + up, sD).r;
        v2.y = LightCubeShadowMap.SampleCmpLevelZero(LightCubeShadowComparsionSampler, forward + up, sD).r;
        v2.z = LightCubeShadowMap.SampleCmpLevelZero(LightCubeShadowComparsionSampler, forward + right + up, sD).r;
        
        
        return dot(v0 + v1 + v2, .1111111f);
}


// UE4: http://ift.tt/1KSCHRC
static const float2 DiscSamples5[]=
{ // 5 random points in disc with radius 2.500000
        float2(0.000000, 2.500000),
        float2(2.377641, 0.772542),
        float2(1.469463, -2.022543),
        float2(-1.469463, -2.022542),
        float2(-2.377641, 0.772543),
};

float _sampleCubeShadowPCFDisc5(float3 L, float3 vL)
{
        float3 SideVector = normalize(cross(L, float3(0, 0, 1)));
        float3 UpVector = cross(SideVector, L);

        SideVector *= 1.0 / 512.0;
        UpVector *= 1.0 / 512.0;
        
        float sD = _vectorToDepth(vL, LightNearFar.x, LightNearFar.y);

        float3 nlV = float3(L.xy, -L.z);

        float totalShadow = 0;

        [UNROLL] for(int i = 0; i < 5; ++i)
        {
                        float3 SamplePos = nlV + SideVector * DiscSamples5[i].x + UpVector * DiscSamples5[i].y;
                        totalShadow += LightCubeShadowMap.SampleCmpLevelZero(
                                LightCubeShadowComparsionSampler, 
                                SamplePos, 
                                sD);
        }
        totalShadow /= 5;

        return totalShadow;

}


CubeDepthReslover.fxh
cbuffer Params : register(b0)
{
    float4x4 World;
        float4x4 View[6];
        float4x4 Projection;
};

struct VertexInput
{
        float4 Position : SV_POSITION;
        //uint InstanceID : SV_InstanceID;
};

struct VertexOutput
{
        float4 Position : SV_POSITION;   
        //uint InstanceID : SV_InstanceID;
};

struct GeometryOutput
{
        float4 Position : SV_POSITION;   
        uint RTIndex : SV_RenderTargetArrayIndex;
};

VertexOutput DefaultVS(VertexInput input)
{
        VertexOutput output = (VertexOutput)0;

        float4 worldPosition = mul(input.Position, World);
        output.Position = worldPosition;
        //output.InstanceID = input.InstanceID;

        return output;
}

[maxvertexcount(18)]
void DefaultGS( triangle VertexOutput input[3], inout TriangleStream<GeometryOutput> CubeMapStream )
{
        [unroll]
    for( int f = 0; f < 6; ++f )
    {
                {
                GeometryOutput output = (GeometryOutput)0;

                        output.RTIndex = f;

                        [unroll]
                        for( int v = 0; v < 3; ++v )
                        {
                                float4 worldPosition = input[v].Position;
                                float4 viewPosition = mul(worldPosition, View[f]);
                                output.Position = mul(viewPosition, Projection);

                                CubeMapStream.Append( output );
                        }

                        CubeMapStream.RestartStrip();
        }
    }
}

technique CubeDepthResolver
{
        pass DefaultPass
        {
                Profile = 10.0;
                VertexShader = DefaultVS;
                GeometryShader = DefaultGS;
                PixelShader = null;
        }
}

Если возникнут вопросы или нужна будет помощь — с радостью помогу, контакты можно посмотреть в моем профиле.

Ближайшие планируемые статьи:

  • Реализация Deferred Rendered Water
  • Physically-Based Rendering без использования IBL

P.S.
Дорогой читатель, если ты любишь внимательно читать статьи и нашел очепятку или неточность- не спеши строчить комментарий, а лучше напиши мне личным сообщением, я обязательно скажу спасибо!

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

пятница, 5 июня 2015 г.

Разбираемся с поддержкой x64 в WPE Pro

image

Думаю, что большинство из местных обитателей знакомы с понятием сниффера. Несмотря на то, что конечная цель у них одна и та же (перехват пакетов, соответствующих определённым критериям), достигают они её совершенно разным образом. Какой-то софт слушает указанный сетевой интерфейс (например, Wireshark, где это реализовано при помощи библиотеки Pcap), а какой-то — перехватывает вызовы ответственных за взаимодействие с сетью WinAPI-функций. И у того, и у другого метода есть свои плюсы и минусы, однако если по задаче необходим перехват пакетов от конкретного заранее известного приложения, то второй вариант, как правило, банально удобнее. В этом случае нет нужды узнавать IP-адреса и порты, которые использует данная программа (особенно учитывая тот факт, что их может быть довольно много), и можно просто сказать «я хочу перехватывать все пакеты вот этого приложения». Удобно, не правда ли?

Пожалуй, самым популярным на сегодняшний день сниффером, работающим по принципу перехвата вызовов определённых WinAPI-функций, является WPE Pro. Возможно, многие из вас слышали о нём на различных форумах, посвящённых онлайн-играм, ведь именно для получения преимуществ в различных играх этот сниффер в большинстве случаев и используется. Свою задачу он выполняет прекрасно, однако у него есть один неприятный недостаток — он не умеет работать с 64-битными приложениями. Так уж вышло, что по одной из возникших задач мне как раз понадобилось перехватывать пакеты от 64-битного приложения, и я посмотрел в сторону Wireshark. К сожалению, использовать его в данной ситуации было не очень удобно — исследуемое приложение отправляло данные на разные IP-адреса, каждый раз открывая новый порт. Погуглив немного, я обнаружил, что готовых аналогов WPE Pro с поддержкой x64 нет (если они всё же есть, буду признателен за ссылки в комментариях — обратите внимание, что речь идёт о Windows). Автор WPE Pro не оставил никаких контактных данных на официальном сайте и в самом сниффере, так что я принял решение разобраться в этом вопросе самостоятельно.

Как протекал процесс и что из этого вышло, читайте под катом.

Итак, что необходимо сделать в первую очередь? Верно, скачать сам WPE Pro. Сделать это можно на официальном сайте сниффера, где предлагаются для загрузки сразу две версии — 0.9a и 1.3. Мы будем рассматривать версию 0.9a, потому что именно она работает на последних версиях Windows.

Скачали? Теперь давайте проверим, не накрыт ли он каким-нибудь паковщиком или протектором:

image

image

Похоже, на этот раз снимать нам ничего не придётся. Тогда берём в руки OllyDbg и загружаем «WpePro.net.exe». Давайте для примера запустим 64-битную версию Dependency Walker'а и узнаем, почему WPE Pro не может отобразить его в списке доступных для перехвата процессов.

Получить список текущих процессов в WinAPI можно двумя основными путями:


При желании можете почитать о разнице между данными способами, например, тут.

Смотрим на intermodular calls модуля «WpePro.net.exe» и видим, что ни CreateToolhelp32Snapshot, ни EnumProcesses тут нет. Возможно, приложение получает их адрес в run-time при помощи WinAPI-функции GetProcAddress, так что давайте посмотрим на referenced text strings. На этот раз всё с точностью наоборот — нашлась как строка «CreateToolhelp32Snapshot», так и «EnumProcesses». Ставим бряки на места, где происходит обращение к данным строкам, нажимаем на кнопку «Target program» в WPE Pro и смотрим на место, на котором мы остановились:

image

Если понажимать F9, то мы увидим, что этот же бряк срабатывает ещё несколько раз, прежде чем наконец появится окно со списком текущих процессов. Давайте посмотрим, почему в нём не оказалось Dependency Walker'а. Закрываем окно, снова нажимаем на кнопку «Target program» и выполняем пошаговую отладку, начиная с того же самого бряка. Вскоре после вызовов GetProcAddress мы попадаем в цикл, в котором последовательно вызываются следующие функции — OpenProcess, EnumProcessModules, скрывающаяся за инструкцией CALL DWORD PTR DS:[EDI+10], GetModuleFileNameEx (инструкция CALL DWORD PTR DS:[ECX+14]) и CloseHandle:

image

Давайте поставим бряк по адресу 0x0042A910, где происходит занесение на стек последнего аргумента функции OpenProcess — PID, и попробуем дождаться момента, когда регистр EAX примет значение, равное идентификатору процесса Dependency Walker'а. На моей машине в этот момент он был равен 6600, т.е. 0x19C8.

Если пробежаться по коду, то мы увидим, что в случае Dependency Walker'а инструкция TEST EAX,EAX, находящаяся по адресу 0x0042A942 и следующая за вызовом WinAPI-функции EnumProcessModules, заставляет следующий за ней оператор условного перехода прыгнуть по адресу 0x0042A9E7, где находится вызов CloseHandle, что, разумеется, не является нормальным ходом работы приложения, ведь мы ещё даже не дошли до вызова функции GetModuleFileNameEx:

image

Обратите внимание на код ошибки — 0x12B (ERROR_PARTIAL_COPY). Давайте посмотрим, почему такое может происходить. Открываем документацию к функции EnumProcessModules и видим следующее:

If this function is called from a 32-bit application running on WOW64, it can only enumerate the modules of a 32-bit process. If the process is a 64-bit process, this function fails and the last error code is ERROR_PARTIAL_COPY (299)


Да, это как раз наш случай.

Несмотря на то, что в документации к функции GetModuleFileNameEx не сказано ничего похожего, ведёт она себя аналогичным образом, что описано, например, тут. Одним из вариантов решения этой проблемы является использование функции QueryFullProcessImageName, которая будет отрабатывать нормально даже в случае вызова из 32-битного приложения, запущенного в WOW64, в случае применения её к 64-битному процессу.

Теперь мы можем занопить вызов функции EnumProcessModules

0042A93F   .  FF57 10       CALL DWORD PTR DS:[EDI+10]               ;  EnumProcessModules
0042A942   .  85C0          TEST EAX,EAX
0042A944      90            NOP
0042A945      90            NOP
0042A946      90            NOP
0042A947      90            NOP
0042A948      90            NOP
0042A949      90            NOP
0042A94A   .  8B4424 14     MOV EAX,DWORD PTR SS:[ESP+14]
0042A94E   .  BE 00000000   MOV ESI,0


и написать code cave для замены GetModuleFileNameEx функцией QueryFullProcessImageName:
0042A971     /E9 DA3F0600   JMP WpePro_n.0048E950
0042A976     |90            NOP
0042A977     |90            NOP
0042A978     |90            NOP
0042A979     |90            NOP                                      ;  GetModuleFileNameEx
0042A97A     |90            NOP
0042A97B     |90            NOP
0042A97C     |90            NOP
0042A97D     |90            NOP
0042A97E     |90            NOP
0042A97F     |90            NOP
0042A980     |90            NOP
0042A981     |90            NOP
0042A982     |90            NOP
0042A983     |90            NOP
0042A984     |90            NOP
0042A985     |90            NOP
0042A986     |90            NOP
0042A987     |90            NOP
0042A988     |90            NOP
0042A989     |90            NOP
0042A98A     |90            NOP
0042A98B     |90            NOP
0042A98C     |90            NOP
0042A98D     |90            NOP
0042A98E   > |6A 14         PUSH 14
0042A990   . |E8 7FCF0300   CALL WpePro_n.00467914

0048E950      60            PUSHAD
0048E951      9C            PUSHFD
0048E952      8D5C24 AC     LEA EBX,DWORD PTR SS:[ESP-54]
0048E956      C74424 AC 040>MOV DWORD PTR SS:[ESP-54],104
0048E95E      53            PUSH EBX
0048E95F      50            PUSH EAX
0048E960      6A 00         PUSH 0
0048E962      55            PUSH EBP
0048E963      E8 777CB675   CALL KERNEL32.QueryFullProcessImageNameA
0048E968      9D            POPFD
0048E969      61            POPAD
0048E96A    ^ E9 1FC0F9FF   JMP WpePro_n.0042A98E


Теперь WPE Pro отображает множество новых процессов, в том числе и наш Dependency Walker:

image

Однако при попытке приаттачиться к данному процессу WPE Pro выдаёт неприятное для нас сообщение:

image

Тут мы уже, к сожалению, ничего не можем поделать. DLL-инъекция осуществляется путём внедрения своей DLL в адресное пространство target-процесса, а на MSDN сказано следующее:

On 64-bit Windows, a 64-bit process cannot load a 32-bit dynamic-link library (DLL). Additionally, a 32-bit process cannot load a 64-bit DLL


Несложно догадаться, что WPE Pro поставляется только с 32-битной библиотекой.

Что же делать? Писать свой перехватчик WinSock? А почему бы и нет?

Но как мы это будем осуществлять? Первое, что приходит на ум — это воспользоваться Detours, однако на официальном сайте сразу же сообщается, что поддержка 64-битных приложений есть только в Professional Edition:

Detours Express is limited to 32-bit processes on x86 processors


Тогда давайте попробуем EasyHook. Среди всего прочего, эта библиотека как раз позволяет работать с 64-битными приложениями:

You will be able to write injection libraries and host processes compiled for AnyCPU, which will allow you to inject your code into 32- and 64-Bit processes from 64- and 32-Bit processes by using the very same assembly in all cases


С небольшими изменениями примера, продемонстрированного в официальном туториале, получаем код, перехватывающий вызовы функций recv и send из WinSock и выводящий перехваченные сообщения побайтово в шестнадцатеричном виде:

Код программы, совершающей DLL-инъекцию

using EasyHook;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.Remoting;
using System.Text;
using System.Threading.Tasks;

namespace Sniffer
{
    public enum MsgType { Recv, Send };

    [Serializable]
    public class Message
    {
        public byte[] Buf;
        public int Len;
        public MsgType Type;
    }

    public class InjectorInterface : MarshalByRefObject
    {
        public void OnMessages(Message[] messages)
        {
            foreach (Message message in messages)
            {
                switch (message.Type)
                {
                    case MsgType.Recv:
                        Console.WriteLine("Received {0} bytes via recv function", message.Len);
                        break;

                    case MsgType.Send:
                        Console.WriteLine("Sent {0} bytes via send function", message.Len);
                        break;

                    default:
                        Console.WriteLine("Unknown action");
                        continue;
                }

                foreach (byte curByte in message.Buf)
                {
                    Console.Write("{0:x2} ", curByte);
                }

                Console.WriteLine("=====================");
            }
        }

        public void Print(string message)
        {
            Console.WriteLine(message);
        }

        public void Ping()
        {

        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length != 1)
            {
                Console.WriteLine("Usage: Sniffer.exe [pid]");
                return;
            }

            int pid = Int32.Parse(args[0]);

            try
            {
                string channelName = null;
                RemoteHooking.IpcCreateServer<InjectorInterface>(ref channelName, WellKnownObjectMode.SingleCall);

                RemoteHooking.Inject(
                   pid,
                   InjectionOptions.DoNotRequireStrongName,
                   "WinSockSpy.dll",
                   "WinSockSpy.dll",
                    channelName);

                Console.ReadLine();
            }
            catch (Exception ex)
            {
                Console.WriteLine("An error occured while connecting to target: {0}", ex.Message);
            }
        }
    }
}


Код самой DLL, которая будет инжектиться в целевой процесс
using EasyHook;
using Sniffer;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace WinSockSpy
{
    public class Main : EasyHook.IEntryPoint
    {
        public Sniffer.InjectorInterface Interface;
        public LocalHook RecvHook;
        public LocalHook SendHook;
        public LocalHook WSASendHook;
        public Stack<Message> Queue = new Stack<Message>();

        public Main(RemoteHooking.IContext InContext, String InChannelName)
        {
            Interface = RemoteHooking.IpcConnectClient<Sniffer.InjectorInterface>(InChannelName);
        }

        public void Run(RemoteHooking.IContext InContext, String InChannelName)
        {
            try
            {
                RecvHook = LocalHook.Create(
                    LocalHook.GetProcAddress("Ws2_32.dll", "recv"),
                    new DRecv(RecvH),
                    this);

                SendHook = LocalHook.Create(
                    LocalHook.GetProcAddress("Ws2_32.dll", "send"),
                    new DSend(SendH),
                    this);

                RecvHook.ThreadACL.SetExclusiveACL(new Int32[] { 0 });
                SendHook.ThreadACL.SetExclusiveACL(new Int32[] { 0 });
            }
            catch (Exception ex)
            {
                Interface.Print(ex.Message);
                return;
            }

            // Wait for host process termination...
            try
            {
                while (true)
                {
                    Thread.Sleep(500);

                    if (Queue.Count > 0)
                    {
                        Message[] messages = null;
                        lock (Queue)
                        {
                            messages = Queue.ToArray();
                            Queue.Clear();
                        }
                        
                        Interface.OnMessages(messages);
                    }
                    else
                    {
                        Interface.Ping();
                    }
                }
            }
            catch (Exception)
            {
                // NET Remoting will raise an exception if host is unreachable
            }
        }

        //int recv(
        //  _In_  SOCKET s,
        //  _Out_ char   *buf,
        //  _In_  int    len,
        //  _In_  int    flags
        //);
        [DllImport("Ws2_32.dll")]
        public static extern int recv(
            IntPtr s,
            IntPtr buf,
            int len,
            int flags
        );

        //int send(
        //  _In_       SOCKET s,
        //  _In_ const char   *buf,
        //  _In_       int    len,
        //  _In_       int    flags
        //);
        [DllImport("Ws2_32.dll")]
        public static extern int send(
            IntPtr s,
            IntPtr buf,
            int len,
            int flags
        );

        [UnmanagedFunctionPointer(CallingConvention.StdCall,
            CharSet = CharSet.Unicode,
            SetLastError = true)]
        delegate int DRecv(
            IntPtr s,
            IntPtr buf,
            int len,
            int flags
        );

        [UnmanagedFunctionPointer(CallingConvention.StdCall,
            CharSet = CharSet.Unicode,
            SetLastError = true)]
        delegate int DSend(
            IntPtr s,
            IntPtr buf,
            int len,
            int flags
        );

        static int RecvH(
            IntPtr s,
            IntPtr buf,
            int len,
            int flags)
        {
            Main This = (Main)HookRuntimeInfo.Callback;
            lock (This.Queue)
            {
                byte[] message = new byte[len];
                Marshal.Copy(buf, message, 0, len);
                This.Queue.Push(new Message { Buf = message, Len = len, Type = MsgType.Recv });
            }

            return recv(s, buf, len, flags);
        }

        static int SendH(
            IntPtr s,
            IntPtr buf,
            int len,
            int flags)
        {
            Main This = (Main)HookRuntimeInfo.Callback;
            lock (This.Queue)
            {
                byte[] message = new byte[len];
                Marshal.Copy(buf, message, 0, len);
                This.Queue.Push(new Message { Buf = message, Len = len, Type = MsgType.Send });
            }

            return send(s, buf, len, flags);
        }
    }
}


Конечно, тут ещё есть, над чем поработать:
  • Во-первых, отсутствует перехват WSARecv, WSASend и некоторых других функций, которые могут использоваться в целевых приложениях
  • Во-вторых, отсутствие GUI и «строкового» представления перехваченных сообщений
  • etc

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

Послесловие


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

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

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

[Из песочницы] Установка OpenStreetMap Nominatim для нахождения широты и долготы по введенному адресу

image

Хотел бы поведать свою историю об установке геокодера Nominatim на выделенный сервер. Изначально предполагалось, что эта задача займёт у меня около 5-7 часов, но не тут то было… Поэтому было решено написать статью c описанием разворачивания Nominatim на сервер до полной работоспособности сайта. Но обо всём по порядку.

Введение


Есть множество сервисов с геокодерами, не буду здесь их всех перечислить, отмечу, что на Хабре есть хорошие статьи по этому поводу: «Geocoding with PHP and the Google Maps API», а также «Яндекс Карты: Поиск произвольных объектов».

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

Документация


Сайт с уже работающей картой можно найти по ссылке: OpenStreetMap Nominatim
А вот и ссылка на примеры запросов: Wiki nominatim
Так же есть ссылка на установку Nominatim Installation. Но эта документация немного устарела, для установки именно по этой статье необходимо немного потанцевать с бубном. Всё ниже описанное, будет ссылаться на эту статью по установке, только без участия бубна.
А так же есть докер контейнер nominatim docker. По какой-то причине, скорее всего из-за устаревших пакетов установки, этот контейнер у меня таки не запустился.

Шаг 1: Создание виртуальной машины


За основу была взята машина из облака Microsoft Azure серии standart D2 с характеристиками: 2 cores, 7 GB RAM, 100 GB SSD. Именно на этой машине и будет производиться установка Nominatim. 100GB на самом деле — это больше чем нужно для моей задачи, так как карта Украины не настолько большая. После установки всех необходимых компонентов осталось свободных 70%.

Шаг 2: Установка необходимых пакетов

sudo apt-get update
sudo apt-get -y install wget
sudo apt-get -y install build-essential automake
sudo apt-get -y install libxml2-dev
sudo apt-get -y install libgeos-dev
sudo apt-get -y install libpq-dev
sudo apt-get -y install libbz2-dev
sudo apt-get -y install libtool libproj-dev
sudo apt-get -y install libgeos++-dev
sudo apt-get -y install gcc proj-bin libgeos-c1 git osmosis
sudo apt-get -y install php5 php-pear php5-pgsql php5-json
sudo apt-get -y install bc
sudo apt-get -y install postgresql-9.4 postgresql-9.4-postgis-2.1 postgresql-contrib-9.4 postgresql-server-dev-9.4
sudo apt-get -y install libboost-chrono1.55-dev libboost-thread1.55-dev libboost-filesystem1.55-dev
sudo apt-get -y install python-software-properties && add-apt-repository -y ppa:kakrueger/openstreetmap && apt-get update && apt-get --no-install-recommends install -y osm2pgsql


Шаг 3: Настройка

sudo -i
pear install DB
sudo -u username
sudo mkdir -p /app/nominatim
sudo -i
useradd -m -p password1234 -d /app/nominatim nominatim
chown nominatim: /app/nominatim
cd /app/nominatim
wget http://ift.tt/1BPfV4S
tar xvf Nominatim-2.4.0.tar.bz2
rm Nominatim-2.4.0.tar.bz2
mv Nominatim-2.4.0/* .
rm Nominatim-2.4.0/
sudo -u nominatim ./autogen.sh
./configure && make


Думаю, в этих строках команд не сложно разобраться. В случае если возникнет ошибка с получение прав доступа, установим права доступа на чтение и запуск на выполнение всем пользователям и группам chmod -R 755 /app. Хотелось бы отметить, что иногда возникала проблема при выполнении команды make. если у вас возникла эта проблема воспользуйтесь командой sudo make clean, а затем уже ./configure && make.

Шаг 4: PostgreSQL


Изначально, postgres настроен не для боевого сервера, так что надо его конфигурировать вот советы от wiki документации nominatim:

Ubuntu location /etc/postgresql/9.x/main/postgresql.conf
CentOS location /var/lib/pgsql/data/postgresql.conf

shared_buffers (4GB)
maintenance_work_mem (10GB)
work_mem (50MB)
effective_cache_size (24GB)
synchronous_commit = off
checkpoint_segments = 100
checkpoint_timeout = 10min
checkpoint_completion_target = 0.9
The numbers in brackets behind some parameters seem to work fine for 32GB RAM machine

sudo passwd postgres
sudo usermod -a -G sudo postgres
service postgresql start && pg_dropcluster --stop 9.4 main
service postgresql start && pg_createcluster --start -e UTF-8 9.4 main

service postgresql start && sudo -u postgres psql postgres -tAc "SELECT 1 FROM pg_roles WHERE rolname='nominatim'" | grep -q 1 || sudo -u postgres createuser -s nominatim && sudo -u postgres psql postgres -tAc "SELECT 1 FROM pg_roles WHERE rolname='www-data'" | grep -q 1 || sudo -u postgres createuser -SDR www-data && sudo -u postgres psql postgres -c "DROP DATABASE IF EXISTS nominatim"


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

Шаг 5: Загрузка и установка osm карт


В начале необходимо загрузить саму карту из сайта в формате *.pbf: OpenStreetMap Data Extracts. В качестве примера я загружаю карту Украины и переименовываю в data.pbf.
wget --output-document=data.pbf http://ift.tt/1BPfTdl
chown nominatim: data.pbf
touch local.php /app/nominatim/settings/local.php
nano /app/nominatim/settings/local.php


Последняя команда открывает файл конфигураций по установке nominatim. В файл local.php нужно ввести код который описан ниже.
<?php
   // Paths
   @define('CONST_Postgresql_Version', '9.4');
   @define('CONST_Postgis_Version', '2.1');
   
   // Website settings
   @define('CONST_Website_BaseURL', 'http://ift.tt/1BPfV4W');
?>


В качестве BaseURL необходимо вписать адрес сайта, с которого будет запущен nominatim.

Теперь запускаем команду установки nominatim. Эта операция занимает довольно таки длительное время, например с картой Украины установка длилась около 7 часов. В силу того, что я выполнял все команды через ssh, и являюсь подверженным переменному отключению интернета, выполнение команды установки проводится в скрине.

screen
service postgresql start && sudo -u nominatim -- ./utils/setup.php --osm-file /app/nominatim/data.pbf --all --threads 2 2>&1; sudo -u nominatim -- ./utils/setup.php --index --create-search-indices


Для выхода из скрина нужно набрать комбинации Ctrl+A затем Ctrl+D. Команда screen -r возвращает обратно в скрин.

Теперь запустим сайт быстрым способом:

./utils/setup.php --create-website /var/www/html
rm /var/www/html/index.html
/etc/init.d/apache2 restart


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

Тестируем сайт


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

image

Проверим как работает api, сделав запрос:
http://ift.tt/1KRA3vD
Response:

[{"place_id":"1145869","licence":"Data © OpenStreetMap contributors, ODbL 1.0. http:\/\/http://ift.tt/1rjkWxu","osm_type":"relation","osm_id":"1413934","boundingbox":["46.342707","46.6291187","30.6114013","30.8313753"],"lat":"46.4858883","lon":"30.68365101101","display_name":"Одесса, Одесская область, Украина","class":"place","type":"city","importance":0.45,"icon":"http:\/\/geocoder.cloudapp.net\/images\/mapicons\/poi_place_city.p.20.png","address":{"city":"Одесса","county":"Одесса","state":"Одесская область","country":"Украина","country_code":"ua"}}]


Выводы


В конце хотелось бы подметить, что для загрузки карты Земли нужно сервер по мощнее, чем серия D2, если вы не хотите *неделю ждать загрузки. Эту проблему так же можно решить с помощью временного масштабирования сервера до серии D14 (16 ядер, 112 ГБ памяти). А вот время выполнение запроса поиска очень радует: в среднем поиск по Украине занимает всего 300мс.

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

Если у кого-то из хабросообщества будет необходимость в рабочем Docker контейнере с Nominatim, или другие идеи — пишите, всегда рад пообщаться.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

[Из песочницы] Почему в России так мало committers в крупные open source проекты

Всю свою недолгую профессиональную карьеру я с удовольствием работал с крупными Open Source фреймворками — Lucene, Solr, Hadoop (map-reduce и yarn), Spark, Zeppelin, IPython, etc. Выбирая между разработкой проприетарного продукта и чего-то на основе open source, я всегда выбираю open source по следующим причинам:

Джедай-разработка. Джедай — это в первую очередь человек, который может в одиночку изменить судьбу вселенной (не подпадающий под принцип «один в поле не воин»). И некоторые open source фреймворки позволяют решать сложные технические проблемы простым деплоиментом готовых решений. Теоретически можно написать свой map-reduce, свою распределенную файловую систему и даже свою supertable realtime database. Но это займет много времени и будет по качеству хуже существующих решений.

А вот свой Spark за пределами долины уже не написать — просто слишком сложная система, требующая слишком много очень высококвалифицированных разработчиков. Но зачем все это писать, если весь big data стек организации можно поднять на 2 дня. Террабайты логов? Cassandra + Spark + Zeppelin. Из готовых docker контейнеров опытный человек может поставить все и за один день.
image

— Apache Spark релизится раз в 3 месяца с мажорными фичами. Это радикальное увеличение стабильности, появление новых инструментов (SparkSQl, Dataframe, GraphX), увеличение количества реализованных алгоритмов (Gradient boosting в MLLib). Solr за пару лет научился шардироваться и посему работать с большими данными. Hadoop переродился в Yarn. Эти фреймворки обзаводятся новой полезной функциональностью без приложения моих усилий. А значит, я могу более эффективно решать поставленные перед мной задачи. В проприетарном продукте жизнь становилось бы легче только тогда, когда я бы сильно вкладывался в то, чтобы сделать ее легче.

Хорошая документация. Очень мало top level apache проектов с плохой документацией. В apache incubator плохую документацию можно встретить чаще. Но даже в этом случаи — в силу открытости проекта у него есть пользователи, которые оставляют следы своих изысканий на StackOverflow. То что в проприетарном проекте обычно первый шаг — обратится непосредственно к автору кода, является самым крайним шагом в open source. За 2 года своего самого тесного общения со spark мне пришлось писать на dev mailing list всего дважды.

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

Работа на себя. Работая с open source вы увеличиваете свою экспертизу в нем и быстро растете в зарплатно-профессиональном плане. Действительно, если понадобиться сменить работу — на рынке есть 5 контор, технологический стек которых вы уже примерно знаете и можете приносить пользу с первого дня. Вам не нужно по полгода входить в контекст переходя с одного проприетарного стека на другой. И фирмам тоже проще — можно нанять сотрудников, которых практически не надо обучать.

Все это является плюсом для сотрудников и работодателей в России. И для того, чтобы воспользоваться этими преимуществами, не надо быть committer. Достаточно быть contributor. Для тех, кто не знает, кратко расскажу, чем они отличаются. Сontributor — это человек, который предложил патч к проекту и его committer вмержил в мастер. Committer — это человек, который имеет право (и обязанность) регулярно коммитить и вмерживать патчи в мастер.

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

Контрибьютером быть классно — тебе не надо сдавать Spark Certification за 300 баксов, при этом никто не поставит под сомнение твою компетентность в этом фреймворке.

Committer обладает большей экспертизой в проекте, но куда важнее — большей властью.

image

Он может «протащить» в проект патч, выгодный его работодателю. Он может забанить патч, если он не выгоден. Он может определять пути развития проекта. Но власть идет не бесплатно. Он реально должен работать над формированием и поддержанием своего авторитета — читать бесконечные, бесполезные патчи, писать архитектурные гугло-доки, отвечать на вопросы. Делать это в свободное время почти нереально — это громадный труд. Поэтому коммитер делает это за счет работодателя. А что работодателю с этого? Посмотрим на список коммитеров в Spark:

Aaron Davidson Databricks
Andrew Or Databricks
Andrew Xia Alibaba
Andy Konwinski Databricks
Ankur Dave UC Berkeley
Charles Reiss UC Berkeley
Cheng Lian Databricks
Davies Liu Databricks
Haoyuan Li UC Berkeley
Imran Rashid Cloudera
Jason Dai Intel
Joseph Bradley Databricks
Joseph Gonzalez UC Berkeley
Josh Rosen Databricks
Kay Ousterhout UC Berkeley
Mark Hamstra ClearStory Data
Matei Zaharia Databricks, MIT
Michael Armbrust Databricks
Mosharaf Chowdhury UC Berkeley
Mridul Muralidharam Yahoo!
Nick Pentreath Mxit
Patrick Wendell Databricks
Prashant Sharma Imaginea, Pramati, Databricks
Ram Sriharsha Hortonworks
Reynold Xin Databricks
Robert Evans Yahoo!
Ryan LeCompte Quantifind
Sandy Ryza Cloudera
Sean McNamara Webtrends
Sean Owen Cloudera
Shane Huang National University of Singapore
Shivaram Venkataraman UC Berkeley
Stephen Haberman Bizo
Tathagata Das Databricks
Thomas Dudziak Groupon
Thomas Graves Yahoo!
Xiangrui Meng Databricks
Yin Huai Databricks

Spark зарождался в UC Berkley, поэтому вычтем всех из Berkley. Databricks — компания, которую образовали основатели Spark, зарабатывает на Databricks Cloud — analytic tool поверх Спарка. Spark является по факту их главным продуктом, поэтому они должны в него вкладываться. Yahoo всегда строила свою инфраструктуру на отрытых решениях — сначала это был Hadoop, теперь Spark. Компаниям такого рода нужны коммитеры по следующим причинам:
  • В инфраструктуру на этом фреймворке у них вложены по крайне мере десятки миллионов долларов (кластеры по тысячи машин в Yahoo). Контроля такого рода вложений не бывает слишком много. Нельзя допускать изменений в проекте, которые не позволят перейти на более новую версию в силу обратно несовместимых изменений или архитектурных решений, которые не укладываются в видение компании;
  • В любой большой компании обычно приходится делать локальные патчи в open source, чтобы заставить работать для специфических условий или требований. Если эти патчи будут большими и серьезным, это создаст проблемы при переходе на новую версию. Поэтому такие патчи надо стараться вмерживать в upstream. Протащить большой патч в большой проект, без того, чтобы какой либо коммитер был в этом заинтересован практически невозможно;
  • Компания видит свои приоритеты и коммитер старается транслировать эти приоритеты в комьюнити.

Я не знаю наверняка, но думаю, что Alibaba имеет не меньше инвестиций в инфраструктуру, чем Yahoo. Groupon меньше, но все же. Для ClearData Spark — основной движок.

Intel нужно точно знать, что Spark хорошо совместим с Intel. Cloudera, Hortonworks — являются вендорами хадупа (а значит и Спарка). Они должны транслировать не только свои интересы, но и интересы заказчика. Компании, для которых Big Data и IT — основной бизнес, куда сильнее заинтересованы в committers. MapR, SAP, Oracle, IBM — сейчас активно ищут коммитеров Cпарка (хотя я не понимаю, как можно активно искать всего 30 людей, которых все поименно знают). И они готовы платить хорошие деньги. Стать комитером Спарка в долине — гарантировано поднять свою зарплату в 2 раза, если она была уже высока.

Компании, которые готовы платить большие деньги за коммитеров, в России отсутствуют. IT-интеграторы не имеют размах IBM и SAP не только в плане оборота, но и в плане амбиций определять развитие отрасли. Они следуют тенденциям, формируемым в долине. Committer просто не сможет принести им пользы.

Продуктовые же компании в России либо малы, либо сидят на проприетарном технологическом стеке. Yandex пытается развиваться по модели Google, где вся разработка in house. Как я понимаю, это позиция основана на идеи, что разработка внутри быстрее и эффективнее любого open source, когда компания в состоянии создать критическую массу опытных специалистов. С деталями инфраструктуры ВКонтакте не знаком, но она тоже проприетарная. Одноклассники точно пользуются Spark, почему их не видно в коммьюнити — не могу сказать.

Таким образом, будучи committer, выиграть по деньгам или возможностям в России на фоне простого contributor считаю очень сложным.

Отсутствие компаний в России, которые хотели бы держать committers (которым было бы выгодно), считаю основной проблемой низкого количества committers.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Изучаем внутренности сервера Huawei RH5885 V3 (unboxing)

Всем привет! Сегодня спустимся с облаков на землю, поговорим о железках и посмотрим, что и как устроено внутри сервера Huawei Tecal RH5885H V3.

image

Именно на этой модели серверов развернут кластер виртуализации IaaS-сервиса CloudLITE.ru. Напомним, что на одних и тех же серверах у нас живут гипервизор и распределенная виртуальная СХД VMware VSAN.

Прежде, чем перейдем непосредственно к unboxing, пара слов о том, почему выбрали именно эту модель. Нет, дело не в азиатском импортозамещении.

1. В своих кластерах виртуализации мы используем 4-сокетные серверы, поскольку они позволяют выделять большее количество ядер на 1 виртуальную машину. Этот сервер удовлетворяет этому параметру.

2. Сервер нужен был под конкретную архитектуру с использованием решения VSAN. Как мы уже упоминали в прошлом посте, у VMware есть список совместимого оборудования, пригодного для построения виртуальной СХД. Среди этой выборки нужных нам 4-сокетных серверов не так уж и много.

3. Эта модель, в отличие от аналогов, доступных на российском рынке, работает на процессорах серии Intel Xeon E 7.

4. Конфигурация кластера подразумевала наличие 24 дисков на одном сервере. Эта модель как раз позволяет создать три дисковой группы, в каждой из которой будет 7 жестких дисков и 1 SSD (в нашем случае их место занимают PCIe флеш-карты).

5. Наконец, соотношение цена качество. Понятно, что все 4-сокетные серверы – это enterprise-решения, которые имеют свою цену. Но на рынке даже в этом сегменте есть модели, оцененные выше или ниже. Выбранная модель как раз дает большое количество возможностей, но за меньшую стоимость.

6. А еще он с салатовыми вставками (наш корпоративный цвет), и вот тут мы не устояли :).

Итак, приступим.

image

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

image

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

image

Теперь пройдемте внутрь. Благодаря модульной структуре, сервер разбирается без каких-либо инструментов. Что особенно порадовало, так это подробнейшие и очень наглядные схемы по сборке\разборке модулей сервера: везде есть пояснения, как и за что хвататься, маркировка составных элементов сервера (дисковые модули, процессоры и пр.). В общем, не заблудишься.

image

Справа – два модуля расширения для установки PCIe карт.

image

В нашей конфигурации используются 4 PCIe карты – 3 PCIe SSD ES3000 и 1 PCIe Ethernet адаптер. Интерфейсная плата CN21ITGAA000 выполнена на базе Intel 82599 и имеет 2 х 10G внешних сетевых интерфейса. Еще 2 х 10G адаптер интегрирован на материнскую плату.

image

Идем дальше. По середине – отсек с 4 процессорами Intel Xeon E7-4830 v2. Слева – отсек с картами памяти и установленными RDIMM.

image

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

image

Для управления сервером и его обслуживания доступны все стандартные на сегодняшний день технологии — BMC, IPMI, SOL, KVM over IP и VirtualMedia.

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

image

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

image

На этом все. Что можем сказать по итогам 6 месяцев эксплуатации: серверы работают стабильно и не доставляют никаких проблем с эксплуатацией и обслуживанием. Не это ли то самое главное в железе?

P.S: со следующей недели мы временно приостанавливаем бесплатный тестовый доступ к CloudLITE.ru (желающих очень много), но до конца недели все еще можно зарегистрироваться и получить в тест до конца июня 1 vCPU, 1 GB RAM, 50 GB HDD. Торопитесь!

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Анимируй это: обзор шаблона для WordPress

Привет, Хабр! В этом посте мы бы хотели разобрать один из наших свежих шаблонов из магазина TemplateMonster, созданный на базе собственного фреймворка для WordPress – Cherry. Мы выбрали для обзора тему автора PixelBuddha, доступную в каталоге под номером 52089.


Остальные WordPress шаблоны из каталога можно посмотреть по ссылке: http://ift.tt/1R0wgN0

Этот шаблон хорошо подойдёт для личного блога, портфолио или для создания Landing page, он лёгок в настройке и управлении. Главной фишкой темы является анимация — много анимаций. Разработчики постарались на славу: анимирован буквально каждый элемент страницы — ссылки, изображения-превью, кнопки и т.д. Используются как простые: плавное изменение цвета или эффекты «всплытия», так и сложные эффекты: изменение размеров изображений, переворачивающиеся картинки-кнопки, которые напоминают анимацию из Material Design, и другие. В том числе анимации подвергся и скроллинг страницы. Простой дёрганный скроллинг в браузере был заменён плавной прокруткой страницы.

Одно из преимуществ новизны — шаблон поддерживает респонсивный дизайн страниц. Блог с установленной темой будет одинаково хорошо смотреться как на девайсах с шириной экрана 2K, так и на мобильных телефонах с шириной 320px, как у iPhone 4.

Навигация по сайту на телефоне организована не менее удобно, чем на компьютере — в форме выпадающего списка, в котором сохранена иерархия страниц. Список реализован с помощью HTML тега «select», поэтому проблем с касаниями по экрану не возникает. К сожалению, такая навигация доступна только мобильным телефонам, на экранах шириной 768 пикселей и более появляется большая «компьютерная» навигационная панель, с выпадающими списками по наведению мыши, поэтому на планшетах, управляться с сайтом не так удобно, как хотелось бы. Но это не должно стать проблемой, навигация по подпунктам доступна напрямую со страницы, хотя для этого и требуется произвести одну дополнительную загрузку.

В дополнение к WordPress форматам были добавлены форматы записей «аудио» и «видео», а также более 80 различных шорткодов, которые упрощают вёрстку страниц с контентом для сайта.

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

Более интересные опции предлагает родительская тема-фреймворк Cherry. Во-первых, данный фреймворк позволит настроить цвета, шрифты, картинки на сайте под свои потребности. Во-вторых, страницы блога и портфолио можно настроить под свой вкус. В арсенале Cherry имеется четыре схемы страницы для блога: панель слева, панель справа, без панели и кирпич и другие настройки отображения контента, такие как лайки, дизлайки, количество комментариев и т.д. Портфолио содержит две схемы страницы: сетка и кирпич. Кроме того, страница с портфолио имеет встроенную навигацию по категориям без перезагрузки страницы, новый контент подгружается автоматически, а уже существующий контент перестраивается на свои места, всё это сопровождается красочной анимацией. В-третьих, встроенный в фреймворк плагин Cherry помогает сайту работать с SEO: настройка файла robots.txt и карты сайта.

Внутри фреймворка Cherry уже находятся несколько заготовленных виджетов, которые можно разместить в произвольном месте на сайте. В первую очередь Cherry предлагает виджеты для добавления персональных рекламных объявлений: Cherry 125x125 Ads и Cherry Banner. Также виджет для социальных сетей: Facebook, Twitter, Flickr, LinckedIn, Delicious, YouTube и Google Plus. Кроме того, из Flickr и Instagram можно выводить фотографии из своего профиля, обнвляться они будут автоматически, для этого созданы соответствующие виджеты Cherry Flickr и Cherry Instagram.

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

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

После установки темы, будет предложено установить дополнительные плагины Cherry Lazy Load Boxes Plugin для динамической загрузки изображений по мере прокрутки страницы вниз, Cherry MediaParallax Plugin для создания эффекта параллакс при прокрутке страницы, Contact Form 7, Newsletter, MotoPress Content Editor.

Наибольший интерес представляет плагин Cherry Lazy Load. Он позволяет загружать страницу без загрузки изображений, тем самым позволяет снизить время ожидания до загрузки сайта. Изображения будут подгружены по мере прокрутки страницы вниз. Однако эффект будет заметен только при медленном интернет-соединении или при использовании на сайте изображений высокой чёткости.

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

Официально тема Communications Company с кодовым номером 52089 поддерживает 5 языков, среди которых, кроме русского и английского, также присутствуют немецкий, испанский и итальянский.

Лицензия этой темы на один сайт в интернет магазине TemplateMonster стоит $75.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Как определить дубликаты картинок с помощью PHP

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

Потому рано или поздно встает вопрос автоматизации процесса поиска повторов, и тут мы рассмотрим основные, а также попробуем в деле.

Сравнение файлов через функцию hash


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

Простой пример вычисления хеша изображения:

<?php
imagecreatefrompng('image.png');
echo hash_file('md5', 'image.png');
?>


Результат выглядит примерно так: bff8b4bc8b5c1c1d5b3211dfb21d1e76

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

ImageMagick


Функция обработки изображений Imagick::compareImages возвращает массив, который содержит восстановленное изображение и разницу между изображениями.

Пример использования при сравнении двух изображений:

<?php
header("Content-Type: image/png");
$image1 = new imagick("image1.png");
$image2 = new imagick("image2.png");
$result = $image1->compareImages($image2, Imagick::METRIC_MEANSQUAREERROR);
$result[0]->setImageFormat("png");

echo $result[0];
?>


В итоге две сравниваемые картинки лепятся в одну, на которой видны отличия.
Также можно получить числовое выражение отличий по каждому параметру (пример с оф.сайта):
-> compare -verbose -metric mae rose.jpg reconstruct.jpg difference.png
Image: rose.jpg
 Channel distortion: MAE
  red: 2282.91 (0.034835)
  green: 1853.99 (0.0282901)
  blue: 2008.67 (0.0306503)
  all: 1536.39 (0.0234439)

gd2 и libpuzzle


Для быстрого поиска дубликатов необходимо установить библиотеки gd2 и libpuzzle.

Установка gd2:

apt-get install libpuzzle-php php5-gd


Установка libpuzzle:
sudo apt-get install libpuzzle-php


Libpuzzle создана для быстрого поиска визуального сходства изображений (GIF, PNG, JPEG). Сначала растровая картинка разбивается на блоки — автоматически отбрасываются рамки, не несущие особо значимой информации. Разница между смежными блоками формирует вектор — это так называемая подпись картинки. Похожесть картинок определяется расстоянием между двумя такими векторами. Потому обычно изменение цвета, ресайз или сжатие не влияют на результаты, выдаваемые libpuzzle.

Libpuzzle довольно проста в использовании. Вычисление подписи для двух изображений:

$cvec1 = puzzle_fill_cvec_from_file('img1.jpg');
$cvec2 = puzzle_fill_cvec_from_file('img2.jpg');


Вычисление расстояния между подписями:
$d = puzzle_vector_normalized_distance($cvec1, $cvec2);


Проверка изображений на схожесть:
if ($d < PUZZLE_CVEC_SIMILARITY_LOWER_THRESHOLD) {
  echo "Pictures are looking similar\n";
} else {
  echo "Pictures are different, distance=$d\n";
}


Сжатие подписей для хранения в базе данных:
$compress_cvec1 = puzzle_compress_cvec($cvec1);
$compress_cvec2 = puzzle_compress_cvec($cvec2);

Перцептивный хеш


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

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

Установка для UNIX платформ выглядит так:

$ ./phpize
$ ./configure [--with-pHash=...] 
$ make
$ make test
$ [sudo] make install

Попробовать на деле можно через i.onthe.io/phash. Загрузка изображений через интерфейс и на выходе показатель «одинаковости».

Как это работает


Получаем хеш первого изображения:
$phash1 = ph_dct_imagehash($file1);


Получаем хеш второго изображения:
$phash2 = ph_dct_imagehash($file2);


Получаем расстояние Хэмминга между двумя изображениями:
$dist = ph_image_dist($phash1,$phash2);

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

Например, при зеркальном отражении — картинка остается неузнанной.
Зато с цветами можно играться сколько угодно — на результат сравнения это не повлияет.
Чего нельзя сказать о манипуляциях с RGB-каналами, Джона опять не узнали, хоть и расстояние Хэмминга для такого случая гораздо меньше.

Остальные результаты выглядят так:

Не мешают (расстояние Хэмминга = 0) Мешают (расстояние Хэмминга — в скобках)
Измененное имя файла Кроп (34)*
Формат (JPEG, PNG, GIF) Поворот 90° (32)**
Оптимизация Google PageSpeed Зеркальное отражение (36)
Ресайз с сохранением пропорций и без Изменение положения кривых в RGB-каналах (18)
Изменение цветовой гаммы и четкости

*зависит от величины кропнутой области. При отрезании от картинки маленькой рамки толщиной в несколько пикселей, расстояние Хэмминга будет нулевым, следовательно сходство — 100%. Но чем ощутимее кроп — тем больше расстояние — тем меньше шансов обнаружить дубликат. О поиске кропнутых дубликатов через перцептивные хеши можно почитать тут.

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

Конспект


  1. Для сравнения картинок используйте ImageMagick, а для поиска полностью идентичных — сравнение через хеш.
  2. Чтобы находить незначительно измененные изображения — используйте библиотеку libpuzzle.
  3. Сравнение через перцептивный хеш — одно из самых надежных, можно попробовать тут.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.