...

суббота, 6 января 2018 г.

[recovery mode] Spectre и Meltdown

Все как всегда, слышим звон, но не знаем где он.

В сети произошел очередной слив информации об двух уязвимостях в аппаратуре современных процессоров. Собственно уязвимость была открыта для публичного обсуждения одна, но методов ее эксплуатации было раскрыто два, под именами Spectre и Meltdown.
Для специалистов эта проблема оборудования известна давно, она «втихую» эксплуатировалась и все были довольны…

По какой то неизвестной причине о ней было решено раздуть шумиху, причем дилетантским способом, те кто выпускал официальные пресс-релизы (http://ift.tt/2EORJIX и http://ift.tt/2E2DVt1) явно плохо представляли как все это работает и где проблема.
А поставщики исходной информации не удосужились подробно объяснить проблему, видимо передали работающий эксплоит и краткое описание для дилетантов…

Доморощенные комментаторы и знатоки ассемблера начали с умным видом обьяснять как все это работает.
Вот например статья, с кучей комментариев http://ift.tt/2Ascbfk.
Особенно умиляет это:
image
«Наивный чукотский парень» не понимает для чего сдвиг на 1000h, да и вообще как все это работает, просто переводит своими словами такой же безграмотный текст оригинального пресс-релиза.

Придется объяснять.

Реальное место расположения уязвимости в аппаратуре

Уязвимость расположена здесь, она выделена красным кружком:
image
Проблема в блоке TLB, который используется в пейджинговой адресации оперативной памяти. Он является ассоциативным буфером хранящим пары значений виртуальный-физический адрес используемой приложением в оперативной памяти.
Это не Кеш данных….

Вот как его описывает в документации фирма Интел:
image
Блок TLB нужен для того чтобы сократить время обращения к ОП за счет исключения процедуры трансляции адресов памяти. Такая трансляция производится единственный раз для всей страницы (размером как раз 4К в случае современных ОС). Все остальные обращения программы к памяти в этой странице уже происходят форсированно, с использованием сохраненного значения в этом буфере. Эти блоки есть во всех современных процессорах, собственно из-за этого все они и подвержены уязвимостям называемым теперь Spectre и Meltdown.
И кстати, спекулятивное выполнение команд не единственная возможность эксплуатации этих уязвимостей, можно задействовать механизм форвардной загрузки данных из ОП.

Блок TLB не имеет программного (микропрограммного) управления, как обычный кеш. Из него невозможно удалить запись, либо принудительно внести/модифицировать какую-либо запись.

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

Соответственно произведенная спекулятивно операция тоже оставит в блоке TLB cлед в виде записи, нужно только эту запись обнаружить и идентифицировать. А это уже «дело техники», технология давно известна и применяется, она называется «Исследование состояния аппаратуры методом временного прозвона». Не ищите этого названия в Интернете, метод был известен в очень узких кругах совершенно не публичных специалистов.

А вот теперь с ним будут знакомы все…

Единственная возможность программного воздействия на блок TLB, — это полный сброс всех его значений (ну тут я немного лукавлю исключительно для простоты понимания). Все патчи уязвимостей Spectre и Meltdown предложенные производителями ПО это и делают, перезагружая в обработчике исключения GP регистр CR3.
После сброса TLB процессору приходится заново выполнять трансляции всех используемых страниц виртуальной памяти, это и снижает производительность системы.

Как все это эксплуатируется в реальности

Первым делом нужно в программе создать буфер размером 2 мегабайта, причем в этот буфер нельзя писать/читать до проведения атаки и он должен располагаться на границе 4К блока. Этот размер принципиален, в буфере должны разместиться ровно 256 страниц по 4К (специфика пейджинговой адресации).

Затем выполняются команды типа:

image

В программе после команды Mov al, [адрес ядра ОС]; произойдет прерывание и значение регистра al не изменится (останется нулевым).
А вот в буфере TLB из-за форвардного запроса выполнения операции произойдет запоминание вычисленного соответствия виртуального адреса и физического адреса [ebx+eax].
Сбросить эту конкретную запись, как это делается в обычном Кеше, в блоке TLB невозможно и она останется…

Соответственно если мы узнаем адрес страницы размером 4К в буфере размером 2М, запомненный в блоке TLB, то мы узнаем и значение прочитанное из ядра ОС в регистр al.
Это уже дело техники, «прозвоним» блок TLB. Для этого нужно по одному разу прочитать все его страницы по 4К, их ровно 256, выполним 256 операций чтения (одну на каждый 4К блок). При этом будем измерять время выполнения операции чтения. Та страница, которая будет читаться быстрее остальных, и будет иметь номер в буфере размером 2М соответствующий прочитанному значению байта из ядра ОС.

Это произойдет потому, что в буфере TLB для этой страницы уже вычислено соответствие между виртуальным и физическим адресом, а для всех остальных страниц этой операции выполнено еще не было.
В «прозвоне» конечно не все так просто, там много нюансов, но это вполне возможно.
Делали, знаем…

Вот собственно и все.

Let's block ads! (Why?)

Мобильные устройства изнутри. Разметка памяти, структура файлов описания и разметки памяти

Restate — или как превратить бревно Redux в дерево

[Из песочницы] Python, под пиратским флагом

image Йо-хо-хо, хабровчане!

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

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


Сразу стоит сделать некоторые ремарки:

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

Соль задачи


Есть игра GuildWars2, в ней запущен рождественский эвент (мини игра), которая очень похожа на guitar hero только с колокольчиком.

image

Смысл игры заключается в своевременном нажатии кнопок-нот 1-2-3-4 и 6-7-8-9 в зависимости от того, какой кружок по какой дорожке приедет к центру, где стоит персонаж. Если очень интересно, то можно посмотреть видео на ютубе, набрав guild wars 2 choir bell.

За полное прохождение, пусть и с погрешностями, дают максимальное количество ценных подарочков, которые можно продать на рынок по цене примерно 5.5 серебра за штуку. Я подсчитал, что за сутки неприрывного прохождения этого эвента, можно делать ~3300 подарочков а это больше 180 чистого золота, цена которому 2 рубля за ед. на черном рынке. Копейки в абсолютных величинах но очень неплохо в сравнении с тем же криптомайнингом, а? Особенно если учесть, что для этого нам не требуется дорогостоящая видеокарта или платный аккаунт.

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

Шаг 0. Анализируем


Для автоматизации нам нужно всего 2 вещи: распознавать пиксели и нажимать кнопки.
Тут требуется сделать небольшое отступление и сказать, что я совсем не будучи программистом, пробовал быдлокодить на с++, с#, PHP, delphi и даже ассемблере в среде masm32. Выбор питона на этой стадии был почти случайным. Я просто подумал «А почему бы не попробовать до кучи на питоне? Вдруг будет удобнее?». Это не был какой то осознанный выбор, я тогда еще и не предполагал, насколько питон классный.

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

Код пипетки спрятал под спойлер
from graphics import *# Здесь и далее импортируемый модуль графических примитивов(скачивается отдельно)
import pyautogui #Здесь и далее импортируемый модуль автоматизации ( скачивается отдельно )
import time # время же. (Стандартный модуль)

def main():# определяем мэйн функцию
    win = GraphWin("pipetka", 200, 200, autoflush=True)#создаем графическую форму размером 200х200 и элементы на ней
    x, y = pyautogui.position()#получаем в x, y координаты мыши
    r, g, b = pyautogui.pixel(x, y)# получаем в r, g, b цвет

    ColorDot = Circle(Point(100, 100), 25)# создаем точку, отображающую цвет
    ColorDot.setFill(color_rgb(r, g, b))# устанавливает ей заливку из ранее полученных цветов
    ColorDot.draw(win)# рисуем на форме win

    RGBtext = Entry(Point(win.getWidth()/2, 25), 10)# создаем RGB вывод
    RGBtext.draw(win)# рисуем на форме win

    RGBstring = Entry(Point(win.getWidth()/2, 45), 10)#создаем вывод цвета в web стиле
    RGBstring.draw(win)# рисуем на форме win

    Coordstring = Entry(Point(win.getWidth() / 2, 185), 10)# создаем отображение координат
    Coordstring.draw(win)# рисуем на форме win


    while True: # цикл перереисовки формы
        time.sleep(0.1)# задержка в 0.1 с, чтобы питон не сходил с ума
        x, y = pyautogui.position()#получаем в x, y координаты мыши
        r, g, b = pyautogui.pixel(x, y)# получаем в r, g, b цвет
        ColorDot.setFill(color_rgb(r, g, b))#Обновляем цвет
        RGBtext.setText(pyautogui.pixel(x, y))#Обновляем RGB
        RGBstring.setText(color_rgb(r, g, b))#Обновляем web цвет
        Coordstring.setText(str(x)+" "+ str(y) )#Обновляем координаты
        win.flush()# Даем команду на перерисовку формы
#основной код начинается ниже.

main()#вызываем нашу функцию.


image

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

1) мало времени на определение цвета и посыл нажатия

2) цвета кружков очень неоднородные

3) незначительные отклонения в позиционировании камеры сильно мешают

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

image

Шаг 1. Кодим распознавание кружка


Код анализатора
import time# модуль времени ( стандартный )
import pyautogui# автоматизатор ( скачивается отдельно )
import winsound#модуль для сигнала, чтобы знать, что программа запустилась ( стандартный )
import keyboard#модуль для работы с клавиатурой ( скачивается отдельно )



def analyzer():

    etalon = [pyautogui.pixel(355, 288), pyautogui.pixel(460, 200), pyautogui.pixel(600, 130),\
        pyautogui.pixel(735, 112), pyautogui.pixel(875, 109), pyautogui.pixel(1000, 145), \
         pyautogui.pixel(1139, 203), pyautogui.pixel(1260, 290) ]#массив эталонных цветов

    trigger = [0,0,0,0,0,0,0,0]#массив триггеров, инициализируем нулями



    while True:

        change = [pyautogui.pixel(355, 288), pyautogui.pixel(460, 200), pyautogui.pixel(600, 130),\
        pyautogui.pixel(735, 112), pyautogui.pixel(875, 109), pyautogui.pixel(1000, 145), \
         pyautogui.pixel(1139, 203), pyautogui.pixel(1260, 290) ] #Забираем в цикле change массив текущего состояния точек

        for nomer in range(0,8):
            if change[nomer][0] > etalon[nomer][0]+50 or change[nomer][1] > etalon[nomer][1]+50 or change[nomer][2] > etalon[nomer][2]+50:

                if trigger[nomer] == 0: #если точка изменила цвет по R,G или B больше чем на +50 и триггер прохождения выключен
                    trigger[nomer] = 1#включаем триггер прохождения
            else:
                if trigger[nomer] == 1: #проверяем не включен ли триггер прохождения
                    trigger[nomer] = 0 #обнуляем триггер прохождения
                    print("push " +str(nomer) + time.strftime(' %X'))

#основной код начинается ниже.

keyboard.wait(combination="home")#после старта ждем нажатия клавиши "Home"
winsound.Beep(1000, 100) #сигналим что программа стартанула
analyzer()# запускаем анализ


Разбор алгоритма:
  1. Кладем в массив etalon цвета фона игрового поля, с которым впоследствии будет происходить сравнение цветов массива change Строчка
      
    keyboard.wait(combination="home")
    

    нужна как раз, чтобы забрать эталонные цвета в момент, когда игра уже развернута и камера отцентрирована
  2. Инициализируем массив триггеров trigger. Он нам нужен, поскольку питон отрабатывает много раз, пока кругляш бежит к центру. Кругляш большой а точка в которой мы снимаем показания маленькая. То есть, для того, чтобы один и тот же кругляш мы не зарегистрировали дважды и более.
  3. Запускаем бесконечный цикл в котором мы постоянно забираем значение цветов в массив change и сравниваем цвета каждой из забранных точек с эталонными цветами
    for nomer in range(0,8):
                if change[nomer][0] > etalon[nomer][0]+50 or change[nomer][1] > etalon[nomer][1]+50 or change[nomer][2] > etalon[nomer][2]+50:
    
  4. Если в предыдущем IF'e мы выяснили что цвет точки с номером nomer ярче хотя бы на 50 единиц по любому из значений RGB, то проверяем зажжен ли триггер для этой точки и если нет — зажигаем
        if trigger[nomer] == 0: 
                        trigger[nomer] = 1
    
  5. Если же цвет в постоянно обновляемом change соответствует эталонному цвету, то тут два варианта. Либо все тихо и кружка еще нет, либо кружок только что съехал дальше с проверяемой точки, что и является сигналом для нажатия кнопки. Определяемся мы в итоге по состоянию триггера. Если триггер был зажжен — значит кругляш уехал. Обнуляем триггер и пока условно нажимаем кнопку.
                    if trigger[nomer] == 1: 
                        trigger[nomer] = 0 
                        print("push " +str(nomer) + time.strftime(' %X'))
    

Шаг 2. Запускаем и тестим


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

Слева — то, что выдал вывод питона, справа — то, что записал я, просматривая видео в замедленном режиме. Как видите есть погрешности в виде лишних нажатий 6 и 0 — это чертовы снежинки, которые никак нельзя убрать. Штука в том, что для исходной цели, непрерывного сбора подарочков, нам не нужен 100% счет, игра прощает игроку довольно много ошибок. Если бы это было не так, то просто нужно было бы ввести дополнительную проверку на белый цвет.

Шаг 3. Кодим нажималку кнопок через потоки


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

Добавляем в код

from threading import Timer

и сам обработчик отложенного нажатия
def delaypress(keynum):
    if keynum < 4:
        keynum +=1
    else:
        keynum +=2
    t = Timer(2, keyboard.send, args=[str(keynum)])
    t.start()

Если на вход поступает номер точки 0-1-2-3 нажимаем через 2 секунды номер точки + 1, если же поступает 4-5-6-7, то нажимаем через 2 секунды номер + 2 ( т.к. кнопка 5 не задействована в мини игре )
Конечный вид кода:
import time# модуль времени ( стандартный )
import pyautogui# автоматизатор ( скачивается отдельно )
import winsound#модуль для сигнала, чтобы значть, что программа запустилась ( стандартный )
import keyboard#модуль для работы с клавиатурой ( скачивается отдельно )
from threading import Timer#импортим таймер из модуля потоков ( стандартный )

def delaypress(keynum):
    if keynum < 4:
        keynum +=1
    else:
        keynum +=2
    t = Timer(2, keyboard.send, args=[str(keynum)])
    t.start()

def analyzer():

    etalon = [pyautogui.pixel(355, 288), pyautogui.pixel(460, 200), pyautogui.pixel(600, 130),\
        pyautogui.pixel(735, 112), pyautogui.pixel(875, 109), pyautogui.pixel(1000, 145), \
         pyautogui.pixel(1139, 203), pyautogui.pixel(1260, 290) ]#массив эталонных цветов

    trigger = [0,0,0,0,0,0,0,0]#массив триггеров, инициализируем нулями



    while True:

        change = [pyautogui.pixel(355, 288), pyautogui.pixel(460, 200), pyautogui.pixel(600, 130),\
        pyautogui.pixel(735, 112), pyautogui.pixel(875, 109), pyautogui.pixel(1000, 145), \
         pyautogui.pixel(1139, 203), pyautogui.pixel(1260, 290) ] #Забираем в цикле change массив текущего состояния точек

        for nomer in range(0,8):
            if change[nomer][0] > etalon[nomer][0]+50 or change[nomer][1] > etalon[nomer][1]+50 or change[nomer][2] > etalon[nomer][2]+50:

                if trigger[nomer] == 0: #если точка изменила цвет по R,G или B больше чем на +50 и триггер прохождения выключен
                    trigger[nomer] = 1#включаем триггер прохождения
            else:
                if trigger[nomer] == 1: #проверяем не включен ли триггер прохождения
                    trigger[nomer] = 0 #обнуляем триггер прохождения
                    #print("push " +str(nomer) + time.strftime(' %X'))#заменяем печать на вызов нажималки
                    delaypress(nomer)


#основной код начинается ниже.

keyboard.wait(combination="home")#после старта ждем нажатия клавиши "Home"
winsound.Beep(1000, 100) #сигналим что программа стартанула
analyzer()# запускаем анализ



Шаг 4. Гребем профит


image

Заключение и ссылки


А теперь, как и обещано, расхваливаю питон( надеюсь достаточно обоснованно ).
  • Питон отлично подходит людям с базовыми знаниями в области программирования и гуглинга для решения интересных задач.
  • Реально юзер френдли язык. Лично по моим ощущениям синтаксиса меньше раза в три и нет идиотского количества ошибок, как в других языках, даже при написании небольших программ.
  • Отличная экосистема. Если вы знаете, как сформулировать вопрос гуглу, вы с большой долей вероятности найдете или готовое решение или инструмент, позволяющей сделать, то, что вы хотите.
  • Нет танцев с бесконечными перекомпиляциями. На современных ПК небольшие программки конечно компилятся быстро, но все равно это раздражает если вы учитесь и приходиться пересобирать проект десятки раз.
  • Документация. По простоте, понятности и лаконичности ближайший аналог видел только у PHP.
  • В питоне прослеживаются черты линукс философии. Даже установка новых модулей легка и приятна и не вызовет у вас трудностей если вы освоили какой нибудь apt или yum
  • Питон удобен для создания всяких хаков и автоматизаций в играх, особенно тех, где требуется частая модификация. Если статья хорошо зайдет — обязательно расскажу как просто на питоне писать в память чужого процесса, делать zoom/speed хак и управлять всем этим безобразием с клавиатуры.

Pyautogui документация
Graphics документация
Документация по модулю keyboard

Если есть интересные идеи для хаков на питоне — пишите в личку.

Let's block ads! (Why?)

[Перевод] Итоги развития компьютерного зрения за один год

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

Полная публикация доступна бесплатно на нашем веб-сайте: www.themtank.org.
Компьютерным зрением обычно называют научную дисциплину, которая даёт машинам способность видеть, или более красочно, позволяя машинам визуально анализировать своё окружение и стимулы в нём. Этот процесс обычно включает в себя оценку одного или нескольких изображений или видео. Британская ассоциация машинного зрения (BMVA) определяет компьютерное зрение как «автоматическое извлечение, анализ и понимание полезной информации из изображения или их последовательности».

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

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

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

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

Прошло более четырёх лет, а варианты CNN по-прежнему составляют основную массу новых нейросетевых архитектур для задач компьютерного зрения. Исследователи переделывают их как кубики конструктора. Это реальное доказательство мощи как open source научных публикаций, так и глубинного обучения. Однако объяснение свёрточных нейросетей легко растянется на несколько статей, так что лучше оставить его для тех, кто более глубоко разбирается в предмете и имеет желание объяснить сложные вещи понятным языком.

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


Для более полного понимания нейросетей и глубокого обучения в целом мы рекомендуем:
  • «Нейросети и глубокое обучение» (Nielsen, 2017) — бесплатный онлайновый учебник, который обеспечивает действительно интуитивное понимание всех сложностей нейросетей и глубокого обучения. Даже чтение первой части должно во многом осветить для новичков тему предмета этой статьи.

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

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

От лица всех участников,
The M Tank



Задача классификации по отношению к изображениям обычно заключается в присвоении метки целому изображению, например, «кот». С учётом этого локализация может означать определение, где находится объект на этом изображении. Обычно тот обозначается неким ограничивающим прямоугольником вокруг объекта. Нынешние методы классификации на ImageNet уже превосходят группы специально обученных людей по точности классификации объектов.

Рис. 1: Задачи компьютерного зрения

Источник: Fei-Fei Li, Andrej Karpathy & Justin Johnson (2016) cs231n, лекция 8 — слайд 8, пространственная локализация и обнаружение (01/02/2016), pdf

Однако увеличение количества классов, вероятно, обеспечит новые метрики для измерения прогресса в ближайшем будущем. В частности, Франсуа Шолле, создатель Keras, применил новые методы, в том числе популярную архитектуру Xception, к внутреннему набору данных Google с более чем 350 млн изображений с множественными метками, содержащими 17 000 классов.

Рис. 2: Результаты классификации/локализации с конкурса ILSVRC (2010–2016)

Примечание: Конкурс ImageNet Large Scale Visual Recognition Challenge (ILSVRC). Улучшение результатов после 2011–2012 гг связано с появлением AlexNet. См. обзор требований конкурса в отношении классификации и локализации
Источник: Jia Deng (2016). Локализация объектов ILSVRC2016: введение, результаты. Слайд 2, pdf

Интересные выдержки с ImageNet LSVRC (2016):

  • Классификация сцены обозначает задачу присвоения меток изображению с определённым классом сцены, таким как «оранжерея», «стадион», «собор» и т.д. В рамках ImageNet в прошлом году прошёл конкурс по классификации сцен на выборке из набора данных Places2: 8 млн изображений для обучения с 365 категориями сцен.

    Победил Hikvision с результатом 9% ошибок топ-5. Система сконструирована из набора глубоких нейросетей в стиле Inception и не-таких-глубоких остаточных сетей.

  • Trimps-Soushen выиграл в задаче классификации ImageNet с ошибкой классификации 2,99% топ-5 и ошибкой локализации 7,71%. Разработчики составили систему из нескольких моделей (усреднив результаты моделей Inception, Inception-Resnet, ResNet и Wide Residual Networks), а в локализации по меткам победил Faster R-CNN. Набор данных был распределён между 1000 классов изображений с 1,2 млн изображений для обучения. Набор данных для тестирования содержал ещё 100 тыс. изображений, которые нейросети раньше не видели.
  • Нейросеть ResNeXt от Facebook финишировала с небольшим отрывом на втором месте с ошибкой классификации 3,03% топ-5. Здесь использовалась новая архитектура, расширяющая оригинальную архитектуру ResNet.


Как можно догадаться, процесс обнаружения объектов делает именно то, что должен делать — обнаруживает объекты на изображениях. Определение обнаружения объектов от ILSVRC 2016 включает в себя выдачу ограничивающих рамок и меток для отдельных объектов. Это отличается от задачи классификации/локализации, поскольку здесь классификация и локализация применяются ко многим объектам, а не к одному доминирующему объекту.

Рис. 3: Обнаружение объектов, где лицо является единственным классом

Примечание: Картинка представляет собой пример обнаружения лиц как обнаружения объектов одного класса. Авторы называют одной из неизменных проблем в этой области обнаружение маленьких объектов. Используя маленькие лица как тестовый класс, они исследовали роль инвариантности размеров, разрешений изображения и контекстуальных обоснований.
Источник: Hu, Ramanan (2016, p. 1)

Одной из главных тенденций 2016 года в области обнаружения объектов стал переход к более быстрым и эффективным системам обнаружения. Это видно по таким подходам как YOLO, SSD и R-FCN в качестве шага к совместным вычислениям на всём изображении целиком. Этим они отличаются от ресурсоёмких подсетей, связанных с техниками Fast/Faster R-CNN. Такую методику обычно называют «тренировкой/обучением от начала до конца» (end-to-end training/learning).

По сути идея состоит в том, чтобы избежать применения отдельных алгоритмов для каждой из подпроблем в изоляции друг от друга, поскольку обычно это повышает время обучения и снижает точность нейросети. Говорится, что такая адаптация нейросетей для работы от начала до конца обычно происходит после работы первоначальных подсетей и, таким образом, представляет собой ретроспективную оптимизацию. Однако техники Fast/Faster R-CNN остаются высокоэффективными и по-прежнему широко используются для обнаружения объектов.

  • SSD: Single Shot MultiBox Detector использует единую нейронную сеть, которая выполняет все необходимые вычисления и устраняет необходимость в ресурсоёмких методах предыдущего поколения. Он демонстрирует «75,1% mAP, превосходя сравнимую самую современную модель Faster R-CNN».
  • Одной из самых впечатляющих разработок 2016 года можно назвать систему, метко названную YOLO9000: Better, Faster, Stronger, в которой используются системы обнаружения YOLOv2 и YOLO9000 (YOLO означает You Only Look Once). YOLOv2 — это сильно улучшенная модель YOLO от середины 2015 года, и она способна показать лучшие результаты на видео с очень высокой частотой кадров (до 90 FPS на изображениях низкого разрешения при использовании обычного GTX Titan X). Вдобавок к повышению скорости, система превосходит Faster RCNN с ResNet и SSD на определённых наборах данных для определения объектов.

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

    Обнаружение объектов YOLOv2 работает на кадрах фильма с Джеймсом Бондом

  • Система Feature Pyramid Networks for Object Detection разработана в научно-исследовательском подразделении FAIR (Facebook Artificial Intelligence Research). В ней применяется «врождённая многомасштабная пирамидальная иерархия глубоких свёрточных нейросетей для конструирования пирамид признаков с минимальными дополнительными затратами». Это означает сохранение мощных репрезентаций без потери скорости и дополнительных затрат памяти. Разработчики добились рекордных показателей на наборе данных COCO (Common Objects in Context). В сочетании с базовой системой Faster R-CNN она превосходит результаты победителей 2016 года.
  • R-FCN: Object Detection via Region-based Fully Convolutional Networks. Ещё один метод, в котором разработчики отказались от применения ресурсоёмких подсетей для отдельных регионов изображения сотни раз на каждой картинке. Здесь детектор по регионам полностью свёрточный и производит совместные вычисления на всём изображении целиком. «При тестировании скорость работы составила 170 мс на одно изображение, что в 2,5–20 раз быстрее, чем у Faster R-CNN», — пишут авторы.

    Рис. 4: Компромисс между точностью и размером объектов при обнаружении объектов на разных архитектурах

    Примечание: По вертикальной оси отложен показатель mAP (mean Average Precision), а по горизонтальной оси — разнообразие мета-архитектур для каждого блока извлечения признаков (VGG, MobileNet… Inception ResNet V2). Вдобавок, малый, средний и большой mAP показывают среднюю точность для малых, средних и крупных объектов, соответственно. По существу, точность зависит от размера объекта, мета-архитектуры и блока извлечения признаков. При этом «размер изображения зафиксирован на 300 пикселях». Хотя модель Faster R-CNN относительно неплохо показала себя в данном примере, важно отметить, что эта мета-архитектура значительно медленнее, чем более современные подходы, такие как R-FCN.
    Источник: Huang et al. (2016, p. 9)

    В вышеупомянутой научной статье представлено подробное сравнение производительности R-FCN, SSD и Faster R-CNN. Из-за сложностей точного сравнения техник машинного обучения мы хотели бы указать на достоинства создания стандартизированного подхода, описанного авторами. Они рассматривают эти архитектуры как «мета-архитектуры», потому что их можно сочетать с разными блоками извлечения признаков, такими как ResNet или Inception.

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

    В научных статьях с описанием SqueezeDet и PVANet ещё раз подчёркивается необходимость компромисса между тенденцией повышения скорости работы приложением со снижением потребляемых вычислительных ресурсов — и сохранением точности, которая требуется для коммерческих приложений реального времени, особенно в приложениях беспилотного автотранспорта. Хотя китайская компания DeepGlint показывала хороший пример обнаружения объектов в реальном времени в потоке с камеры видеонаблюдения.

    Определение объектов, отслеживание объектов и распознавание лиц в системе DeepGlint


    Результаты ILSVRC и COCO Detection Challenge
    COCO (Common Objects in Context) — ещё один популярный набор данных изображений. Однако он относительно меньше по размеру и тщательнее курируется, чем альтернативы вроде ImageNet. Он нацелен на распознавание объектов с более широким контекстом понимания сцены. Организаторы проводят ежегодный конкурс на обнаружение объектов, сегментацию и ключевые точки. Вот результаты с конкурсов ILSVRC и COCO на обнаружение объектов:
    • ImageNet LSVRC, обнаружение объектов на изображениях (DET): Система CUImage показала 66% meanAP. Выиграла в 109 из 200 категорий объектов.
    • ImageNet LSVRC, обнаружение объектов на видео (VID): NUIST 80,8% meanAP
    • ImageNet LSVRC, обнаружение объектов на видео с отслеживанием: CUvideo 55,8% meanAP
    • COCO 2016, обнаружение объектов (ограничивающие рамки): G-RMI (Google) 41,5% AP (абсолютный прирост в 4,2 п.п. по сравнению с победителем 2015 года — MSRAVC)

    В обзоре результатов, показанных системами обнаружения объектов 2016 года, ImageNet пишет, что MSRAVC 2015 установила очень высокую планку по производительности (первое появление сетей ResNet на этом соревновании). Производительность систем улучшилась во всех классах. В обоих конкурсах сильно улучшилась локализация. Достигнуто значительное улучшение на объектах маленького размера.

    Рис. 5: Результаты систем обнаружения на изображениях в конкурсе ILSVRC (2013–2016)

    Примечание: Результаты систем обнаружения на изображениях в конкурсе ILSVRC (2013–2016). Источник: ImageNet 2016, онлайновая презентация, слайд 2, pdf


    Относится к процессу отслеживания конкретного интересующего объекта или нескольких объектов, на заданной сцене. Традиционно этот процесс применяется в видеоприложениях и системах взаимодействия с реальным миром, где наблюдения производятся после обнаружения исходного объекта. Например, процесс критически важен для систем беспилотного транспорта.
    • «Полностью свёрточные сиамские сети для отслеживания объектов» сочетает базовый алгоритм отслеживания с сиамской сетью, обученной от начала до конца, которая достигает рекордных показателей в своей области и работает покадрово со скоростью, превышающей необходимую для работы приложений реального времени. Эта научная статья пытается преодолеть недостаток функциональной насыщенности, доступный моделям отслеживания из традиционных методов онлайнового обучения.
    • «Обучение глубоких регрессионных сетей отслеживанию объектов на 100 FPS» — ещё одна статья, авторы которой пытается преодолеть существующие проблемы с помощью онлайновых методов обучения. Авторы разработали трекер, который применяет сеть с механизмом прогнозирования событий (feed-forward network) для усвоения общих взаимоотношений в связи с движением объекта, его внешним видом и ориентацией. Это позволяет эффективно отслеживать новые объекты без онлайнового обучения. Показывает рекордный результат в стандартном бенчмарке отслеживания, в то же время позволяя «отслеживать общие объекты на 100 FPS».

      Видео работы GOTURN (Generic Object Tracking Using Regression Networks)

    • Работа «Глубокие признаки движения для визуального отслеживания» сочетает вручную написанные признаки, глубокие признаки RGB/внешнего вида (из CNN), а также глубокие признаки движения (обученные на оптическом потоке изображений), чтобы достичь рекордных показателей. Хотя глубокие признаки движения являются обычным делом в системах распознавания действий и классификации видео, авторы заявляют, что они впервые используются для визуального отслеживания. Статья получила награду как лучшая статья на конференции ICPR 2016, в секции «Компьютерное зрение и зрение роботов».

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

    • Статья «Виртуальные миры как промежуточное звено для анализа отслеживания множественных объектов» посвящена проблеме отсутствия присущей реальному миру изменчивости в существующих бенчмарках и наборах данных для отслеживания видео. Статья предлагает новый метод клонирования реального мира с помощью генерации с нуля насыщенных, виртуальных, синтетических, фотореалистичных сред с полным покрытием метками. Этот подход решает некоторые проблемы стерильности, которые присутствуют в существующих наборах данных. Сгенерированные изображения автоматически размечаются точными метками, что позволяет использовать их для разнообразных приложений, помимо обнаружения и отслеживания объектов.
    • «Глобальное оптимальное отслеживание объектов с полностью свёрточными сетями». Здесь обсуждаются разнообразие объектов и помехи как две корневые причины ограничений в системах отслеживания объектов. «Предлагаемый нами метод решает проблему разнообразия внешнего вида объектов при помощи полностью свёрточной сети, а также работает с проблемой помех путём динамического программирования».

Let's block ads! (Why?)

Главу Intel заподозрили в продаже акций компании на $24 млн из-за уязвимости процессоров

Изданию Business Insider стало известно, что глава Intel Брайан Кржанич (Brian Krzanich) продал значительную часть акций компании, уже зная о серьёзной уязвимости выпускаемых ею процессоров. В ноябре топ-менеджер продал принадлежавшие ему акции и опционы компании на $24 млн. На данный момент он владеет всего 250 тысячами акций — это минимум, который он должен иметь в соответствии с контрактом.

Исследователи Google в июне сообщили руководству Intel об уязвимости их продукции. Из-за неё хакеры могут получить доступ к личной информации пользователей компьютеров на базе процессоров Intel, AMD и ARM.

О найденной уязвимости стало известно только 2 января из статьи, опубликованной изданием The Register. После этого акции Intel упали в цене.

Согласно данным Комиссии по ценным бумагам и биржам США (SEC), глава Intel уведомил ведомство о намерениях по продаже акций 30 октября — на этот момент он должен был быть в курсе уязвимостей. Представитель SEC отказался комментировать возможность проверки сделки.

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

Другие материалы по теме финансов и фондового рынка от ITI Capital:


Let's block ads! (Why?)

Разработка на LÖVE

Тахометр или спидометр: Поток мыслей про измерение частоты в Arduino

Предистория

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

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

Картинка для привлечения внимания

// Сразу скажу, что код, указанный в статье не для копипаста, а для иллюстраций. 
// Я конкретно эти примеры не компилировал. У меня код ещё по проекту размазан.

Немного физики

Для измерения частоты вращения нам понадобится датчик положения колеса/вала/круга/итп. Датчик ставится как правило один. Возможно, что он будет срабатывать не один раз на каждый оборот. Например, у вас датчик Холла и 4 магнита на колесе. Таким образом, для правильного вычисления частоты нужно знать:


  • количество срабатываний датчика на один оборот К;
  • минимальная ожидаемая частота Мин.
  • максимальная ожидаемая частота Макс.
Вычисленная = ВычисляемЧастоту() / К;
Если (Частота < Мин) Частота = 0
Иначе Если (Частота < Макс) Частота = Вычисленная 

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

С количеством срабатываний понятно, но зачем ещё эти мины и максы? Давайте рассмотрим сначала варианты расчёта частоты.

Со скоростью всё проще, достаточно знать число π, диаметр колеса, а частоту вращения мы уже знаем.


Болванка для кода

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

const byte fqPin = 2; // Для ATMega32 только 2 или 3.

volatile unsigned long counter = 0;

// Функция для обработки прерывания.
void ISR() {
  // Здесь код прерывания
  counter++; // Например
}

void setup() {
  Serial.begin(115200);
  // Подключаем функцию ISR на прерывание по появлению сигнала на ноге fqPin.
  attachInterrupt(digitalPinToInterrupt(fqPin), ISR, RISING);
}

void loop() {
  // Копируем данные. 
  noInterrupts();
  unsigned long cnt = counter;
  interrupts();
  // Здесь делаем что-то с полученными данными.
  // ...
  Serial.println(cnt);
  delay(1000);
}

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

Функция ISR() вызывается каждый раз, когда появляется единица на ноге fqPin. Мы эту функцию не вызываем, это делает сам контроллер. Он это делает, даже когда основная программа стоит в ступоре на функции delay(). Считайте, что ISR() обслуживает событие, от вас не зависящее и данное вам свыше как setup() и loop(). Контроллер прерывает выполнение вашей программы, выполняет ISR() и возвращается обратно в ту же точку, где прерывал.

Обратите внимание, что в функции loop() мы отключаем прерывания вообще любые для того, чтобы прочитать переменную counter и сохранить её во временную переменную cnt. Потом, конечно же, включаем снова. Так мы можем потерять один вызов, конечно же, но с другой стороны, переменная unsigned long имеет 32 бита, а процессор ATMega32 8-битный, вряд ли он скопирует данные за один такт, а ведь в процессе копирования может случиться прерывание и часть данных изменится. По этой же причине мы копируем значение counter локально так как значение этой переменной при использовании в разных местах программы может быть разным опять же из-за изменения её в прерывании.

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


Считаем обороты за единицу времени.

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

Частота = ( Счётчик / Время ) / К
const byte fqPin = 2; // Для ATMega32 только 2 или 3.
const unsigned long interval = 1000000UL; // Интервал подсчёта в микросекундах
const int K = 1;

unsigned long oldMks = 0; // предыдущий момент времени

volatile unsigned long counter = 0;

// Функция для обработки прерывания.
void ISR() {
  // Здесь код прерывания
  counter++; // Например
}

void setup() {
  Serial.begin(115200);
  // Подключаем функцию ISR на прерывание по появлению сигнала на ноге fqPin.
  attachInterrupt(digitalPinToInterrupt(fqPin), ISR, RISING);
}

void loop() {
  // вычисляем текущий момент времени
  unsigned long mks=microseconds();
  // Получаем данные. 
  noInterrupts();
  unsigned long cnt = counter;
  counter = 0; // Сразу сбросим счётчик
  interrupts();
  // Выводим частоту в оборотах в секунду
  Serial.println( 1000000f * (float)cnt / (float)(mks-oldMks) / (float)K );
  // 1000000 микросекунд в секунде
  // далее по формуле выше.
  // mks-oldMks лучше, чем interval потому, что это реальное время от последнего
  // опроса счётчика, а interval -- предполагаемое.
  // Все целые переменные приводим в вещественные
  oldMks=mks; // Сохраняем время вычисления.
  // Спим пока копятся отсчёты до следующего вычисления
  delayMicroseconds(interval);
}

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

Для повышени точности на малой скорости можно увеличить число К, как это сделано, скажем, в автомобильной технике для датчика ABS. Можно увеличить время подсчёта. Делая и то и другое мы подходим ко второй проблеме — переполнению счётчика. Да, переполнение легко лечится увеличением количества бит, но арифметика процессора Arduino не умеет считать 64-битные числа столь быстро, как хотелось бы и как она это делает с 16-разрядными.

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

Есть другой метод. Он лишён вышеописанных недостатков, но, как водится, имеет свои.


Считаем интервал между отсчётами

Частота = 1 / ( Интевал * К )

Мы можем засечь время одного отсчёта и другого, вычислить разницу. Величина, обратная вычисленному интервалу и есть частота. Круто! Но есть минусы.

const byte fqPin = 2; // Для ATMega32 только 2 или 3.
const int K = 1;

volatile unsigned long interval;

// Функция для обработки прерывания.
void ISR() {
  // Здесь код прерывания
  static unsigned long oldTime; // Сохраняем предыдущее значение.
  unsigned long Time=microseconds();
  interval=Time-OldTime();
  oldTime=Time;
}

void setup() {
  Serial.begin(115200);
  // Подключаем функцию ISR на прерывание по появлению сигнала на ноге fqPin.
  attachInterrupt(digitalPinToInterrupt(fqPin), ISR, RISING);
}

void loop() {
  // Получаем данные. 
  noInterrupts();
  unsigned long cnt = interval;
  interrupts();
  // Выводим частоту в оборотах в секунду
  Serial.println( 1000000f / ( (float)K * (float)(cnt) );
  // 1000000 микросекунд в секунде
  // далее по формуле выше.
  // Все целые переменные приводим в вещественные
  // Спим, чтобы не заливать экран потоком данных
  // Четверть секунд -- хорошее время.
  delay(250);
}

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

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

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

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


Комбинированный метод

Можно попробовать объединить достоинства обоих методов.

Частота = Счётчик / ИнтервалИзмерения / К

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

const byte fqPin = 2; // Для ATMega32 только 2 или 3.
const int K = 1;

volatile unsigned long counter; // Количество отсчётов.
volatile unsigned long mks;     // Время последнего отсчёта.

unsigned long oldTime;  // Время последнего отсчёта в предыдущем вычислении.

// Функция для обработки прерывания.
void ISR() {
  // Здесь код прерывания
  mks=microseconds(); // Момент последнего отсчёта
  counter++;  // Количество отсчётов
}

void setup() {
  Serial.begin(115200);
  // Подключаем функцию ISR на прерывание по появлению сигнала на ноге fqPin.
  attachInterrupt(digitalPinToInterrupt(fqPin), ISR, RISING);
}

void loop() {
  unsigned long rpm;
  // Получаем данные. 
  noInterrupts();
  unsigned long cnt = counter;
  counter = 0;
  unsigned long tmr = mks;
  interrupts();
  // Выводим частоту в оборотах в секунду
  if (cnt > 0) {
    rpm = 1000000UL / ((tmr - oldTime) / counter) / K;
    oldTime = tmr;
  } else {
    rpm = 0;
  }
  Serial.println( rpm );
  delay(250);
}

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

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

Если использовать кооперативную многозадачнось, то можно сделать подсчёт, скажем раз 100мс, а вывод на дисплей раз в 250мс. Очень короткий интервал опроса снизит чувствительность к низким частотам.

Как говорят в рекламе, "но это ещё не всё".


Ошибки дребезга

Для устрашения вас предположу, что измеряем частоту вращения двигателя от индуктивного датчика зажигания. То есть, грубо говоря, на высоковольтный провод намотан кусок кабеля и мы измеряем индукцию в нём. Это довольно распространённый метод, не правда ли? Что же здесь сложного может быть? Самая главная проблема — современные системы зажигания, они дают не один импульс, а сразу пачку.

Примерно так:


Но даже обычная система зажигания даёт переходные процессы:


Старинные же кулачковые контактные вообще показывают замечательные картинки.

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

МинимальныйИнтервал = ( 1 / ( K * МаксимальнаяРазумнаяЧастота) )
const byte fqPin = 2; // Для ATMega32 только 2 или 3.
const int K = 1;
const unsigned long maxFq = 20000; // rpm (оборотов в минуту)
const unsigned long minInterval = 1000000UL / ( K * maxFq ); // минимальный интервал в мкс 

volatile unsigned long counter; // Количество отсчётов.
volatile unsigned long mks;     // Время последнего отсчёта.

unsigned long oldTime;  // Время последнего отсчёта в предыдущем вычислении.

// Функция для обработки прерывания.
void ISR() {
  // Здесь код прерывания
  static unsigned long oldTmr; // сохраняем старый таймер
  unsigned long tmr=microseconds();
  if (tmr - oldTmr > minImterval) {
    mks=microseconds(); 
    counter++;
    oldTmr=tmr;  
  }
}

void setup() {
  Serial.begin(115200);
  // Подключаем функцию ISR на прерывание по появлению сигнала на ноге fqPin.
  attachInterrupt(digitalPinToInterrupt(fqPin), ISR, RISING);
}

void loop() {
  unsigned long rpm;
  // Получаем данные. 
  noInterrupts();
  unsigned long cnt = counter;
  counter = 0;
  unsigned long tmr = mks;
  interrupts();
  // Выводим частоту в оборотах в секунду
  if (cnt > 0) {
    rpm = K * 1000000UL / ((tmr - oldTime) / counter);
    oldTime = tmr;
  } else {
    rpm = 0;
  }
  Serial.println( rpm );
  delay(250);
}

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

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


Особенности измерения скорости движения и скорости вращения.

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

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


Вывод информации

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


Стрелки

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

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

Вот пример с нелинейным выводом показаний:

dispRPM(unsigned int rpm) {
  static unsigned int lastRpm;
  if (rpm > lastRpm) {
    // Увеличивается
    unsigned int disp = rpm - (lastRpm-rpm)/5; // быстро вверх
    outputRPM(disp); // Поворот стрелки
    lastRpm=disp;
  } else {
    // Уменьшается
    unsigned int disp = rpm - (lastRpm-rpm)/2; // медленно вниз
    outputRPM(disp); // Поворот стрелки
    lastRpm=disp;
  }
}

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


Цифры

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

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

Я предлагаю менять частоту вывода информации на дисплей в зависимости от степени изменения величины. Если обороты меняются, скажем, на 5% от последнего подсчёта, а не показа — можно затупить и показывать раз в 300-500мс. Если на 20%, то показывать раз в 100мс.

Можно огрубить шкалу и показывать только две значащие цифры

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

Идеи по отображению цифр тоже приветствуются в комментариях.


Вывод

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

Let's block ads! (Why?)

[Перевод] Истоки Stimulus

От переводчика: Давид Хейнемейер Ханссон написал небольшой текст о том, почему он и его команда Ruby on Rails разработала свой собственный Javascript фреймворк. Оригинал текста размещен в репозитории нового проекта

Мы пишем много Javascript в Basecamp, но мы не используем его для создания "JavaScript-приложений" в современном смысле. Все наши приложения рендерят HTML на стороне сервера, затем мы добавляем вкрапления Javascript, чтобы оживить их.

Это путь величественного монолита. Basecamp работает на множестве платформ, включая нативные мобильные приложения, с единым набором контроллеров, представлений и моделей, созданных под Ruby on Rails. Иметь общий интерфейс, который обновляется из единого места, — это ключ к тому, чтобы маленькая команда работала хорошо, несмотря на множество поддерживаемых платформ.

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

Это не значит, что нет смысла в таком подходе для некоторых людей в каком-то месте. Но как основной подход ко многим видам приложений, и конечно, таким как Basecamp, — это в целом регрессия с точки зрения простоты и продуктивности.

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

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

Это желание привело нас к двойному решению: Turbolinks и Stimulus.


Перед тем как я перейду к Stimulus, нашему скромному JavaScript фреймворку, позвольте мне вкратце пересказать назначение Turbolinks.

Turbolinks происходит от так называемого pjax, разработанного в GitHub. Основная идея остается той же. Причиной, по которой полная перезагрузка страницы кажется медленной, не является то, что браузеру тяжело обработать HTML, отправленный с сервера. Браузеры реально хороши и быстры в этом. То, что обычно HTML-контент больше аналогичного JSON тоже неважно (особенно с учетом gzip). Нет, основная причина в том, что CSS и Javascript должны переинициализироваться и примениться к странице заново. Вне зависимости от того, закешированы ли файлы. Это может быть медленным, если у вас приличный размер CSS и JavaScript.

Чтобы обойти эту переинициализацию, Turbolinks держит постоянный процесс, так же как одностраничные приложения делают это. Но, в основном, это процесс невидимый. Он перехватывает ссылки и загружает новые страницы по Ajax. Сервер по-прежнему возвращает полные HTML-документы.

Эта стратегия в одиночку может сделать большинство действий в приложениях реально быстрыми (если сервер способен отвечать за 100-200мс, что возможно с кешированием). Для Basecamp это ускорило переходы по страницам в 3 раза. Это дает приложению то самое чувство отзывчивости и динамичности, которое являлось большей частью доводов "за" одностраничные приложения.

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

До Stimulus Basecamp использовал смесь разных стилей и паттернов, чтобы добавить эти фичи. Часть кода была просто на jQuery, аналогичная по объему часть была на ванильном JavaScript и несколько большая объекто-ориентированная система. Все они вместе работали через явную обработку событий, опираясь на data-behavior атрибуты.

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


Три основных концепта в Stimulus

Stimulus заворачивает лучшее от этих паттернов в скромный маленький фреймворк, крутящийся вокруг трех основных концептов: контроллеров (controllers), действий (actions) и целей (targets).

Он спроектирован так, чтобы прогрессивно улучшать HTML, для которого он предназначен. Чтобы вы могли взять простой шаблон и посмотреть, какое поведение действует на него. Вот пример:


PIN:

Вы можете прочесть это и получить вполне хорошее представление о том, что происходит. Даже без знания чего-нибудь о Stimulus и взгляда на код самого контроллера. Это почти как псевдокод. Это очень отличается от чтения куска HTML, у которого есть внешний JavaScript-файл, вешающий сюда обработчики событий. Он также обеспечивант разделение сущностей, потерянное во многих современных JavaScript-фреймворках.

Как вы можете видеть, Stimulus не беспокоится о создании HTML. Скорее он цепляет себя на текущий HTML-документ. А HTML в большинстве случаев рендерится на сервере либо по загрузке страницы (первый заход), либо через Ajax-запрос, который меняет DOM.

Stimulus сконцентрирован на манипуляциях с существующим HTML-документом. Иногда это означает добавление CSS-класса, который прячет, анимирует или подсвечивает элемент. Иногда это означает перестановку элементов в группах. Иногда означает манипуляции с контентом элемента, например, преобразование UTC-времени, которое кешируется вместе с контентом, в локальное, показываемое пользователю.

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


Как Stimulus отличается от мейнстримовых JavaScript фреймворков

Это делает Stimulus очень отличающимся от большинства современных JavaScript-фреймворков. Почти все они сосредоточены на превращении JSON в DOM-элементы через какой-то вид языка шаблонов. Большинство использует эти фреймворки чтобы родить пустую страницу, заполненную исключительно элементами созданными через JSON-to-template рендеринг.

Stimulus также отличается в вопросах состояния. Большинство фреймворков имеют способы поддержания состояния внутри JavaScript-объектов, чтобы затем отрендерить HTML, основанный на этом состоянии. Stimulus является полной противоположностью. Состояние хранится в HTML, так что контроллеры могут быть выкинуты между сменой страниц, но они переинициализируются, как только закешированный HTML появляется вновь.

Это значительно отличающаяся парадигма. Я уверен, много опытных JavaScript-разработчиков, кому доводилось работать с современными фреймворками, будут издеваться. Но нет, отстаньте. Если вы счастливы со сложностью и усилиями, которые требуются чтобы поддерживать приложение в водовороте, скажем, React + Redux, то Turbolinks + Stimulus не понравятся вам.

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


Stimulus и похожие идеи из реального мира

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

На самом деле это похоже на тот секретный ингредиент, который мы имели в Basecamp, когда делали Ruby on Rails. Чувство, что современные популярные подходы чрезмерно непрямолинейны, так что мы можем делать больше с меньшими усилиями.

Более того, вам даже не нужно выбирать. Stimulus и Turbolinks прекрасно работают в соединении с другими более тяжелыми подходами. Если 80% вашего приложения не вписывается в сложную установку, попробуйте нашу двухкомпонентную сборку для этого. Затем разверните тяжелую технику для той части приложения, где вы реально выиграете от этого.

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

Этот набор альтернативных фреймворков про избавление от тяжестей насколько это возможно. Чтобы остаться в рамках парадигмы "запрос-ответ" для большинства взаимодействий, которые прекрасно работают с этой простой моделью. Затем обращаемся к дорогим инструментам, где есть смысл для достижения максимального эффекта.

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

Дайте ему шанс!

Ссылка на GiHub проекта

Let's block ads! (Why?)

[Из песочницы] Так почему же ты не участвуешь в разработке Open Source программного обеспечения?

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

Представляем перевод статьи Егора Бугаенко «Why Don't You Contribute to Open Source?»:
В одном из прошлогодних постов я говорил о том, что участие в опенсорс проектах влияет на рыночную стоимость программиста. Если на вашем GitHub профайле нельзя найти информацию о коммитах и участии в большом количестве проектов – то в глазах окружающих ваша ценность как разработчика снижается. Пренебрежение участием в опенсорсе говорит о том, что вы на самом деле не любите программировать, вы просто работаете за деньги. Мой пост вызвал много гневных комментариев, и сегодня я постараюсь на них ответить.

Суть комментариев сводится к следующему утверждению: «Я не работаю в опенсорс проектах, но это не мешает мне питать страсть к разработке». Затем следует список причин, которыми авторы таких комментариев обосновывают свою позицию:

  • Я провожу всё свободное время с семьёй.
  • У меня и так дел хватает, с чего бы заниматься ещё чем-то?
  • Мне хорошо платят, зачем тогда работать бесплатно?
  • Мой наниматель не разрешает заниматься открытыми проектами.
  • Моя компания мне за это не заплатит.

Неплохие оправдания, но давайте взглянем на них под другим углом.
Сейчас невозможно создавать софт без использования компонентов с открытым исходным кодом. Я уверен, спорить не будет никто. Только какие-то базовые вещи могут создаваться без повторного использования кода. Даже не так. Даже малюсенькие программки невыплотимы без открытых исходников. В качестве самого минимума понадобится хотя бы ОС и язык программирования. И в большинстве случаев они обладают открытым исходным кодом (Microsoft – исключение и вообще должна сгинуть).

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

А вы не отдаёте ничего взамен.

И мне интересно, почему?

Просто признайте, что вам всё равно. Это всё же не преступление


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

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

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

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

Я более чем уверен, что в 95% случаев ваш руководитель не будет против вашего вклада в пару используемых в проектах библиотек, если вы объясните, что их хорошо бы немного улучшить. Стоит попробовать.

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

Если вы не уходите из такой компании, вы принимаете такую позицию


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

Завтра, если вас попросят использовать краденый софт, вы скажете, что выбора не было: «Руководитель попросил. Я за авторские права и верю, что авторам должны платить, но мне пришлось украсть, потому что компания попросила». Звучит как хорошее оправдание, правда?

То же и с опенсорсом. Если вам не всё равно и вы питаете страсть к разработке, вы вкладываетесь в разработку открытых проектов или уходите из компании, которая не разделяет ваши взгляды. Что, почему-то не можете уволиться? Тогда не говорите мне об увлечении. Просто признайтесь, что вы слишком слабы, чтобы следовать за своей страстью.

И это тоже не преступление. Такова ваша природа, и всего.

Источник

Let's block ads! (Why?)

пятница, 5 января 2018 г.

31 февраля

31 февраля

Сейчас я изучаю отчёт очередной проверки проекта Chromium и используемых в нём библиотек, с помощью анализатора кода PVS-Studio. По итогам проверки у меня намечается цикл статей, посвященный разбору некоторых видов ошибок, и тому, как их можно избежать. Однако, одна ошибка так сильно понравилась, что я решил не откладывать её описание, а сразу написать маленькую заметку в блог.
Наша команда написала уже 5 статей (1, 2, 3, 4, 5), посвященных поиску ошибок в открытом проекте Chromium. И, видимо, скоро я напишу ещё несколько заметок в наш блог на эту тему.

Сейчас я изучаю отчёт, выданный анализатором PVS-Studio и просто выписываю найденные ошибки. Написание заметок — это уже следующий этап. Я люблю в начале просмотреть отчёт, а потом уже решаю, как и о чём лучше написать. Однако, одна ошибка мне особенно понравилась, и я решил описать её сразу, не откладывая на потом.

Ошибка живёт в библиотеке Protocol Buffers (protobuf), используемой в проекте Chromium. Protocol Buffers — это протокол сериализации (передачи) структурированных данных, предложенный Google как эффективная бинарная альтернатива текстовому формату XML.

Если бы я увидел эту ошибку пару месяцев назад, я бы прошел мимо неё. Ну ошибка и ошибка. Однако, увидев её, я сразу вспомнил эпический сбой кассовых аппаратов, который недавно произошел в России. 20 декабря крупнейшие ритейлеры и сети АЗС по всей России столкнулись со сбоями в работе новых кассовых аппаратов. Первыми о проблеме узнали жители Владивостока, далее она распространилась по стране по мере начала рабочего дня, затронув Новосибирск, Барнаул, Красноярск, Кемерово и другие крупные города.

Ошибка в кассовых аппаратах и ошибка в Protocol Buffers это две разные ошибки, никак не связанные между собой. Но мне захотелось показать, как могут возникать подобные ошибки. Ведь часто дефекты кроятся не в хитрых алгоритмах, а являются следствием банальных опечаток. Я не знаю, что там было напутано в коде касс, но зато знаю, как глупая опечатка приводит к неработоспособности функции ValidateDateTime, предназначенной для проверки даты в Protocol Buffers. Давайте посмотрим на код этой функции и разберёмся, в нём.

static const int kDaysInMonth[13] = {
  0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};

bool ValidateDateTime(const DateTime& time) {
  if (time.year < 1 || time.year > 9999 ||
      time.month < 1 || time.month > 12 ||
      time.day < 1 || time.day > 31 ||
      time.hour < 0 || time.hour > 23 ||
      time.minute < 0 || time.minute > 59 ||
      time.second < 0 || time.second > 59) {
    return false;
  }
  if (time.month == 2 && IsLeapYear(time.year)) {
    return time.month <= kDaysInMonth[time.month] + 1;
  } else {
    return time.month <= kDaysInMonth[time.month];
  }
}

Функция ValidateDateTime принимает на вход дату и должна определить, корректна она или нет. В начале выполняются основные проверки. Проверяется, что номер месяца лежит в диапазоне [1..12], дни находятся в диапазоне [1..31], минуты находятся в диапазоне [0..59] и так далее. В коде всё это хорошо видно, и не требует специальных пояснений.

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

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

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

В общем функция написана просто и красиво. Вот только неправильно.

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

Ещё раз взгляните на код:

if (time.month == 2 && IsLeapYear(time.year)) {
  return time.month <= kDaysInMonth[time.month] + 1;
} else {
  return time.month <= kDaysInMonth[time.month];
}

В сравнениях "time.month <=" надо использовать не член структуры month, а член day. Т.е. корректный код должен выглядеть так:
if (time.month == 2 && IsLeapYear(time.year)) {
  return time.day <= kDaysInMonth[time.month] + 1;
} else {
  return time.day <= kDaysInMonth[time.month];
}

Естественно номер месяца (от 1 до 12), всегда меньше количества дней в любом из месяцев.

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

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

Данная ошибка (вернее две ошибки) обнаруживается с помощью следующих предупреждений PVS-Studio:

  • V547 / CWE-571 Expression 'time.month <= kDaysInMonth[time.month] + 1' is always true. time.cc 83
  • V547 / CWE-571 Expression 'time.month <= kDaysInMonth[time.month]' is always true. time.cc 85

Как видите, теперь PVS-Studio также сразу классифицирует предупреждения согласно Common Weakness Enumeration (CWE).

Ещё хочу обратить внимание, что анализатор PVS-Studio всё более глубоко учится анализировать код. Сама по себе диагностика V547 старая (была реализована в 2010 году). Однако, скажем, год назад анализатор не смог бы найти эту ошибку. Теперь анализатор умеет заглянуть внутрь массива и понять, что извлекаются значения в диапазоне [28..31]. При этом он понимает, что 0 в массиве учитывать не надо, так как возможный диапазон time.month равен [1..12]. Если бы номер месяца был, скажем, равен 100, то произошел выход из функции и анализатор это понимает.

В итоге, анализатор видит, что происходят следующие сравнения диапазонов:

  • [1..12] <= [28..31]
  • [1..12] <= [29..32]

Следовательно, условия всегда истинны, о чём анализатор и выдаёт предупреждение. Вот такой вот глубокий анализ. Как видите, мы не только добавляем новые предупреждения в PVS-Studio, но и дорабатываем Data-Flow анализ, что сказывается на качестве уже существующих диагностик.

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

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

Можно только порекомендовать писать более внимательно юнит-тесты и использовать профессиональные статические анализаторы кода, такие как PVS-Studio.

Спасибо за внимание. Пойду дальше изучать отчёт.

Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov. February 31.

Let's block ads! (Why?)

[Из песочницы] Meltdown: влияет не только на производительность

Обновления безопасности


Компания Microsoft выпустила обновления безопасности для операционных систем Windows, исправляющие критические уязвимости в процессорах Intel, AMD и ARM, которые исправляют раскрытые на днях уязвимости Meltdown и Spectre. Патчи могут привести к снижению производительности на затронутых системах и не только. Ниже будут приведены 2 скриншота и пояснения к ним.

Экспресс-тест на примере 7-Zip


До установки обновления безопасности KB4056890 (версия 1607)
image

После установки обновления безопасности KB4056890 (версия 1607)
image

В первом тесте получаем 8946 MIPS и 3:53 мин, во-втором 8558 MIPS и 4:06 мин. Разница составляет 4,3%. Теряем в производительности ради безопасности (по вине «прекрасных» инженеров Intel) если капнуть глубже, становится ясно, что не только в производительности.

До установки патча температура процессора в пике составляла 72 градуса, после установки уже 86 градусов. Время между замерами 35 минут. В итоге мы получаем не только менее производительный но еще и более горячий CPU (в среднем 10-15%).

Почему процессор стал теплее?


Возможно при работе он чаще сбрасывает спекулятивные инструкции из своего кэша.

Это все?


Нет. Это лишь вершина айсберга, вот что уже известно на сегодняшний день:
  • снижение производительности CPU на 5-30%
  • снижение производительности с операциями чтение/запись
  • увеличенное TDP 10-15% (CPU, HDD)
  • увеличение счетов за электричество на 10-30%
  • проблемы с безопасностью по всему миру на миллиардах устройств
  • упущенная выгода и прочие убытки

И что нас ждет неизвестно. Meltdown, Spectre, IOHIDeous — хорошее начало 2018 го.

Let's block ads! (Why?)

[Из песочницы] Telegram-бот в качестве подарка

[Перевод] Как построить географическую панель наблюдения с данными в реальном времени

В этом посте покажу, как построить интерактивную географическую панель наблюдения с Displayr, Plotly и R. Особенно интересно, что она отслеживает позицию военных самолетов в реальном времени. Для этого я собираюсь взять данные из двух разных источников (регионы на основании размера ВВС и отслеживание позиции самолетов в реальном времени). Панель наблюдения отображает динамические данные двумя способами: оттенок региона (чтобы показать численность ВВС в стране) и точки-маркеры (для позиций самолетов). Потом я построю карту, чтобы аккуратно и красиво отобразить все эти данные.

Считывание табличных данных из сети


Я собираюсь покрасить каждую страну на карте в соответствии с численностью ее ВВС. Для этого нужен список воздушых судов по каждой стране. К счастью, Википедия всегда к нашим услугам, и там есть именно то, что нужно (здесь). Код ниже считывает данные и чистит их для представления в виде удобной таблицы.
library(httr)
library(XML)
 
# считать таблицу по URL
url &amp;lt;- "http://ift.tt/2jIVeuc"
r &amp;lt;- GET(url)
airforces &amp;lt;- readHTMLTable(doc = content(r, "text"))[[2]]
 
# почистить необходимые колонки
airforces &amp;lt;- airforces[-1, c("Country[note 1]", "Military aircraft[note 3]")]
colnames(airforces) &amp;lt;- c("Country", "MilitaryAircraft")
remove.bracket.content &amp;lt;- function(s) {
return(gsub("\\[.+\\]", "", s))
}
airforces &amp;lt;- data.frame(apply(airforces, 2, remove.bracket.content))
airforces$MilitaryAircraft &amp;lt;- as.numeric(gsub(",", "", airforces$MilitaryAircraft))
airforces

Пулинг данных в реальном времени со всей земли


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

Для сопоставления этой информации я строю URL, чтобы получить JSON-объект с информацией о военном самолете (JSON — гибкий текстовый формат для обмена данными). Затем считываю JSON в data.frame.

library(jsonlite)&amp;lt;/pre&amp;gt;
url &amp;lt;- "http://ift.tt/2hJ71op?"
url &amp;lt;- paste0(url, "fMilQ=TRUE")
 
positions &amp;lt;- fromJSON(url)$acList
 
if (length(positions) != 0) {
positions &amp;lt;- positions[positions$Type != "TEST", ]
positions &amp;lt;- positions[!is.na(positions$Lat), ]
}
positions

Раскрашивание стран на карте


Код ниже создает plotly-карту мира. Страны раскрашены сообразно численности ВВС, шкала показана в легенде. В терминологии plotly каждый слой карты называется trace.
library(plotly)
library(flipFormat)
 
# задать область карты и проекцию
g &amp;lt;- list(scope = "world",
showframe = FALSE, showcoastlines = TRUE,
projection = list(type = 'mercator'),
lonaxis = list(range = c(-140, 179)),
lataxis = list(range = c(-55, 70)),
resolution = 50) # разрешение повыше
 
# закрасить страны по размеру ВВС
p &amp;lt;- plot_geo(airforces) %&amp;gt;%
 
add_trace(data = airforces, name = "Airforce",
z = ~MilitaryAircraft, color = ~MilitaryAircraft,
colors = 'Blues', locations = ~Country,
marker = list(line = list(color = toRGB("grey"), width = 0.5)),
showscale = TRUE, locationmode = "country names",
colorbar = list(title = 'Airforce', separatethousands = TRUE)) %&amp;gt;%
config(displayModeBar = F) %&amp;gt;%
 
layout(geo = g,
margin = list(l=0, r=0, t=0, b=0, pad=0),
paper_bgcolor = 'transparent')

Добавление маркеров для самолетов


Наконец, добавим маркеры, показывающие позиции самолетов, как еще один trace. Я использую разные цвета для тех, у которых скорость меньше 200 узлов и высота меньше 610 метров. Больше информации о самолетах во всплывающих подсказках.
aircolors = rep("airborne", nrow(positions))  # создать вектор со статусом каждого самолета
aircolors[positions$Spd &amp;lt; 200 &amp;amp; positions$Alt &amp;lt; 2000] &amp;lt;- "ground/approach"
hovertext = paste0("Operator:", positions$Op, "\nModel:", positions$Mdl, 
"\nAltitide(ft):", sapply(positions$Alt, FormatAsReal))
hoverinfo = rep("all", nrow(positions))
 
p = add_trace(p, data = positions, x = positions$Long, y = positions$Lat, 
 color = aircolors, hovertext = hovertext, showlegend = FALSE)

Вот конечный результат.

Несколько завершающих штрихов


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

Let's block ads! (Why?)

[Перевод] Составляем DNS-запрос вручную

Об авторе. Джеймс Рутли — бэкенд-разработчик в компании Monzo.

В этой статье мы изучим двочиный формат сообщений Domain Name Service (DNS) и напишем вручную одно сообщение. Это больше, чем вам нужно для использования DNS, но я подумал, что для развлечения и в образовательных целях интересно посмотреть, что находится под капотом.

Мы узнаем, как:

  • Написать запросы DNS в двоичном формате
  • Отправить сообщение в теле датаграммы UDP с помощью Python
  • Прочитать ответ от DNS-сервера

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

DNS используется для перевода человекочитаемых доменных имён (таких как example.com) в машиночитаемые IP-адреса (такие как 93.184.216.34). Для использования DNS нужно отправить запрос на DNS-сервер. Этот запрос содержит доменное имя, которое мы ищем. DNS-сервер пытается найти IP-адрес этого домена в своём внутреннем хранилище данных. Если находит, то возвращает его. Если не может найти, то перенаправляет запрос на другой DNS-сервер, и процесс повторяется до тех пор, пока IP-адрес не будет найден. Сообщения DNS обычно отправляются по протоколу UDP.

Стандарт DNS описан в RFC 1035. Все диаграммы и бóльшая часть информации для этой статьи взята в данном RFC. Я бы рекомендовал обратиться к нему, если что-то непонятно.

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


У всех сообщений DNS одинаковый формат:
+---------------------+
|       Заголовок     |
+---------------------+
|        Вопрос       | Вопрос для сервера имён
+---------------------+
|         Ответ       | Ресурсные записи (RR) с ответом на вопрос
+---------------------+
|       Authority     | Записи RR с указанием на уполномоченный сервер
+---------------------+
|     Дополнительно   | Записи RR с дополнительной информацией
+---------------------+

Вопрос и ответ находятся в разных частях сообщения. В нашем запросе будут секции «Заголовок» и «Вопрос».

Заголовок


У заголовка следующий формат:
0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F 
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                      ID                       |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    QDCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    ANCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    NSCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    ARCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

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

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

Для нас имеют значение следующие поля:

  • ID: Произвольный 16-битный идентификатор запроса. Такой же ID используется в ответе на запрос, так что мы можем установить соответствие между ними. Возьмём AA AA.
  • QR: Однобитный флаг для указания, является сообщение запросом (0) или ответом (1). Поскольку мы отправляем запрос, то установим 0.
  • Opcode: Четырёхбитное поле, которое определяет тип запроса. Мы отправляем стандартный запрос, так что указываем 0. Другие варианты:
    • 0: Стандартный запрос
    • 1: Инверсный запрос
    • 2: Запрос статуса сервера
    • 3-15: Зарезервированы для будущего использования
  • TC: Однобитный флаг, указывающий на обрезанное сообщение. У нас короткое сообщение, его не нужно обрезать, так что указываем 0.
  • RD: Однобитный флаг, указывающий на желательную рекурсию. Если DNS-сервер, которому мы отправляем вопрос, не знает ответа на него, он может рекурсивно опросить другие DNS-серверы. Мы хотим активировать рекурсию, так что укажем 1.
  • QDCOUNT: 16-битное беззнаковое целое, определяющее число записей в секции вопроса. Мы отправляем 1 вопрос.

Полный заголовок


Совместив все поля, можно записать наш заголовок в шестнадцатеричном формате:
AA AA - ID
01 00 – Параметры запроса
00 01 – Количество вопросов
00 00 – Количество ответов
00 00 – Количество записей об уполномоченных серверах
00 00 – Количество дополнительных записей

Для получения параметров запроса мы объединяем значения полей от QR до RCODE, помня о том, что не упомянутые выше поля установлены в 0. Это даёт последовательность 0000 0001 0000 0000, которая в шестнадцатеричном формате соответствует 01 00. Так выглядит стандартный DNS-запрос.

Вопрос


Секция вопроса имеет следующий формат:
0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F 
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                                               |
/                     QNAME                     /
/                                               /
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                     QTYPE                     |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                     QCLASS                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

  • QNAME: Эта секция содержит URL, для которого мы хотим найти IP-адрес. Она закодирована как серия надписей (labels). Каждая надпись соответствует секции URL. Так, в адресе example.com две секции: example и com.

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

    Секция QNAME завершается нулевым байтом (00).

  • QTYPE: Тип записи DNS, которую мы ищем. Мы будем искать записи A, чьё значение 1.
  • QCLASS: Класс, который мы ищем. Мы используем интернет, IN, у которого значение класса 1.

Теперь можно записать всю секцию вопроса:
07 65 – у 'example' длина 7, e
78 61 – x, a
6D 70 – m, p
6C 65 – l, e
03 63 – у 'com' длина 3, c
6F 6D – o, m
00    - нулевой байт для окончания поля QNAME 
00 01 – QTYPE
00 01 – QCLASS

В секции QNAME разрешено нечётное число байтов, так что набивка байтами не требуется перед началом секции QTYPE.
Мы отправляем наше DNS-сообщение в теле UDP-запроса. Следующий код Python возьмёт наш шестнадцатеричный DNS-запрос, преобразует его в двоичный формат и отправит на сервер Google DNS по адресу 8.8.8.8:53.
import binascii
import socket


def send_udp_message(message, address, port):
    """send_udp_message sends a message to UDP server

    message should be a hexadecimal encoded string
    """
    message = message.replace(" ", "").replace("\n", "")
    server_address = (address, port)

    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        sock.sendto(binascii.unhexlify(message), server_address)
        data, _ = sock.recvfrom(4096)
    finally:
        sock.close()
    return binascii.hexlify(data).decode("utf-8")


def format_hex(hex):
    """format_hex returns a pretty version of a hex string"""
    octets = [hex[i:i+2] for i in range(0, len(hex), 2)]
    pairs = [" ".join(octets[i:i+2]) for i in range(0, len(octets), 2)]
    return "\n".join(pairs)


message = "AA AA 01 00 00 01 00 00 00 00 00 00 " \
"07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 00 01 00 01"

response = send_udp_message(message, "8.8.8.8", 53)
print(format_hex(response)) 

Можете запустить этот скрипт, скопировав код в файл query.py и запустив в консоли команду $ python query.py. У него нет никаких внешних зависимостей, и он должен работать на Python 2 или 3.
После выполнения скрипт выводит ответ от DNS-сервера. Разобьём его на части и посмотрим, что можно выяснить.

Заголовок


Сообщение начинается с заголовка, как и наше сообщение с запросом:
AA AA – Тот же ID, как и раньше
81 80 – Другие флаги, разберём их ниже
00 01 – 1 вопрос
00 01 – 1 ответ
00 00 – Нет записей об уполномоченных серверах
00 00 – Нет дополнительных записей

Преобразуем 81 80 в двоичный формат:
8    1    8    0
1000 0001 1000 0000

Преобразуя эти биты по вышеуказанной схеме, можно увидеть:
  • QR = 1: Это сообщение является ответом
  • AA = 0: Этот сервер не является уполномоченным для доменного имени example.com
  • RD = 1: Для этого запроса желательна рекурсия
  • RA = 1: На этом DNS-сервере поддерживается рекурсия
  • RCODE = 0: Ошибки не обнаружены

Секция вопроса


Секция вопроса идентична такой же секции в запросе:
07 65 – у 'example' длина 7, e
78 61 – x, a
6D 70 – m, p
6C 65 – l, e
03 63 – у 'com' длина 3, c
6F 6D – o, m
00    - нулевой байт для окончания поля QNAME 
00 01 – QTYPE
00 01 – QCLASS

Секция ответа


У секции ответа формат ресурсной записи:
0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F 
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                                               |
/                                               /
/                      NAME                     /
|                                               |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                      TYPE                     |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                     CLASS                     |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                      TTL                      |
|                                               |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                   RDLENGTH                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
/                     RDATA                     /
/                                               /
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
C0 0C - NAME
00 01 - TYPE
00 01 - CLASS
00 00 
18 4C - TTL
00 04 - RDLENGTH = 4 байта
5D B8 
D8 22 - RDDATA

  • NAME: Этой URL, чей IP-адрес содержится в данном ответе. Он указан в сжатом формате:
    0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F 
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    | 1  1|                OFFSET                   |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    

    Первые два бита установлены в значение 1, а следующие 14 содержат беззнаковое целое, которое соответствует смещению байт от начала сообщения до первого упоминания этого имени.

    В данном случае смещение составляет c0 0c или двоичном формате:

    1100 0000 0000 1100
    

    То есть смещение байт составляет 12. Если мы отсчитаем байты в сообщении, то можем найти, что оно указывает на значение 07 в начале имени example.com.
  • TYPE и CLASS: Здесь используется та же схема имён, что и в секциях QTYPE и QCLASS выше, и такие же значения.
  • TTL: 32-битное беззнаковое целое, которое определяет время жизни этого пакета с ответом, в секундах. До истечения этого интервала результат можно закешировать. После истечения его следует забраковать.
  • RDLENGTH: Длина в байтах последующей секции RDDATA. В данном случае её длина 4.
  • RDDATA: Те данные, которые мы искали! Эти четыре байта содержат четыре сегмента нашего IP-адреса: 93.184.216.34.


Мы увидели, как составить DNS-запрос. Теперь можно попробовать следующее:
  • Составить запрос для произвольного доменного имени
  • Запрос на другой тип записи
  • Отправить запрос с отключенной рекурсией
  • Отправить запрос с доменным именем, которое не зарегистрировано



1. Шестнадцатеричные числа (base 16) часто используются как удобная краткая запись для 4 битов двоичных данных. Вы можете конвертировать данные между этими форматами по следующей таблице:
Десятичный Hex Двоичный Десятичный Hex Двоичный
0 0 0000 8 8 1000
1 1 0001 9 9 1001
2 2 0010 10 A 1010
3 3 0011 11 B 1011
4 4 0100 12 C 1100
5 5 0101 13 D 1101
6 6 0110 14 E 1110
7 7 0111 15 F 1111

Как видите, можно представить любой байт (8 бит) двумя шестнадцатеричными символами.

Let's block ads! (Why?)