...

суббота, 9 мая 2015 г.

Правим баг без исходных кодов

image

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

Что ж, ближе к делу. Есть такая широко известная в узких кругах программа под названием «Govorilka». Как объясняет её автор, это ничто иное, как «программа для чтения текстов голосом». По сути, так оно и есть. При помощи неё было озвучено множество популярных и не очень видео, рапространившихся по всей сети. Программа имеет консольную версию под названием «Govorilka_cp», которую удобно вызывать из своих собственных приложений, что, собственно, я и сделал в одном из своих проектов.

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

Учитывая, что говорилка не обновлялась уже несколько лет, а сам автор оставил вот такое «послание» на своём сайте

image

, я понял, что надеяться мне не на кого, и решать проблему придётся самому.

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

Прежде чем загружать говорилку в OllyDbg, давайте посмотрим, не защищена ли она каким-нибудь протектором. Берём в руки DiE и видим следующую картину:

image

Судя по его выводу, программа запакована ASPack'ом. Для убедительности воспользуемся ещё одним анализатором — PEiD:

image

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

Избавление исполняемого файла от ASPack'а можно разделить на три основных этапа:

  • Поиск OEP (Original Entry Point — адрес, с которого бы начинала выполняться программа, если бы не была упакована)
  • Снятие дампа
  • Восстановление IAT

Начнём, разумеется, с поиска OEP. Один из самых простых способов обнаружить оригинальную точку входа — поставить хардварный бряк на ESP-4, потому что большинство паковщиков после своей работы восстанавливают стек. Запускаем говорилку в OllyDbg, открываем Command Line при помощи Alt-F1 (окно стандартного плагина, который поставляется вместе с самим отладчиком), вводим команду hr esp-4, нажимаем F9 и останавливаемся вот на таком месте:

image

Теперь нам надо пробежаться до ближайшего return'а при помощи Ctrl-F9, нажать F8 и… оказаться на OEP, которая в данном случае находится по адресу 0x0045A210:

image

Пришло время снимать дамп. Скачиваем и устанавливаем плагин для OllyDbg под названием OllyDump, перезапускаем отладку при помощи Ctrl-F2, снова останавливаемся на OEP и выбираем пункт меню «Dump debugged process», который находится в Plugins -> OllyDump:

image

Нажимаем кнопку «Dump», выбираем имя выходного исполняемого файла и… получаем вот это при попытке его запустить:

image

Значит, что-то не так с импортами. Ничего, восстановим их вручную.

Дампим процесс говорилки ещё раз, но на этот раз снимаем галочку с CheckBox'а «Rebuild Import». Скачиваем ImpREC, выбираем процесс говорилки из списка, вводим адрес 5A210 в поле для OEP — 0x0045A210 — image base (0x400000) = 0x5A210 и нажимаем на кнопку «IAT AutoSearch»:

image

Делаем, как сказали, и наблюдаем следующую картину:

image

Очевидно, это ненормально. Пробуем указать RVA и size, которыми нам предлагала воспользоваться в случае неудачи сама программа, и нажать на кнопку «Get Imports» снова:

image

Что-то явно идёт не так, как ожидается. Давайте попробуем найти границы IAT вручную. Ищем в дизассемблированном листинге вызов любой WinAPI-функции. Например, вот этот:

image

Прыгаем на него (left click -> Enter) и видим, куда обращаются все JMP'ы:

image

Переходим по любому из указанных в данном месте адресов (right-click -> Follow in Dump -> Memory address), выбираем соответствующее представление (right-click по окну Memory Dump'а -> Long -> Address) и видим следующее:

image

Пробегаемся глазами до начала списка адресов функций:

image

Нажимаем двойным кликом мыши на ntdll.RtlDeleteCriticalSection и ищем конец списка:

image

Указываем адрес начала IAT (0005F168) и size (0000066C), снова нажимаем на кнопку «Get Imports» и получаем следующий результат:

image

Уже лучше, но всё равно остались невалидные импорты, причём самое странное, что мы вроде бы всё сделали правильно… Давайте воспользуемся другим приложением для восставления IAT — Scylla:

image

Да что такое происходит? А давайте попробуем проделать то же самое в другой системе — например, в Windows 7 x32 (до этого эксперименты проводились в Windows 8 x64).

Чудесным образом мы получили валидные импорты:

image

Честно говоря, не уверен, что это — баг ImpRec'а и Scylla, особенность Windows 8 x64 или что-то ещё, но главное, что мы восстановили IAT. Точнее, для этого нам надо нажать на кнопку «Fix Dump», выбрать сделанный ранее дамп и попробовать запустить получившийся исполняемый файл. Да, при запуске говорилки без аргументов она работает, как и положено, а вот в случае передачи каких-либо фраз для озвучивания оно падает, как и в запакованном варианте.

Итак, ASPack снят. Что дальше? А дальше, собственно, нам предстоит разобраться с возникающим на некоторых компьютерах багом.

Первым делом давайте посмотрим на стандартное окно Windows, сообщающее о краше приложения:

image

Вам должно быть заметно, что Exception Offset равен 0x000591D4. Загружаем говорилку в OllyDbg, ставим софтварный бряк на этот адрес (точнее, на image base + 0x000591D4 — в моём случае это 0x004591D4), нажимаем F9 и получаем следующее:

image

Как вы видите, значение регистра ESI на момент выполнения операции чтения по адресу ESI+38 равно нулю, что, разумеется, приводит к крашу приложения. Двумя инструкциями выше заметно, что в ESI значение попадает из регистра EAX. Значение регистра EAX до этого в данной процедуре не меняется, так что смотрим по Call Stack'у, откуда нас позвали:

image

Прыгаем туда (right click -> Show Call) и видим, что прямо перед вызовом процедуры находится команда MOV EAX,DWORD PTR DS:[45EC5C]:

image

Как видно из предыдущего скриншота, по адресу 0x45EC5C также находится ноль. Ставим хардварный бряк на запись по этому адресу (right click -> Breakpoint -> Hardware, on write -> Dword), запускаем приложение ещё раз и обнаруживаем, что никакой записи по данномуадресу не происходит. Давайте проанализируем, как себя ведёт приложение в случае успешной работы в другой системе. Запись по адресу 0x45EC5C в этом случае действительно происходит:

image

, в результате чего ESI+38 даёт какое-то осмысленное значение. Давайте узнаем, как мы попали в это место. Call Stack на момент выполнения данной инструкции пустой, так что у меня возникло впечатление, что это основная процедура программы. Чтобы понять, в каком именно месте приложение стало вести себя по-другому, давайте запустим Trace over (Ctrl-F12) на обеих системах (там, где приложение падает, и там, где оно работает).

В случае системы, где приложение постоянно падает, оно достигло бряка на доступе к ESI+38, где и упало. Последняя строчка кода в trace log'е была CALL'ом по адресу 0x004597C8:

image

, в то время как в системе, где приложение работает нормально, после вызова данной процедуры (0x004597C8) происходит выполнение других инструкций, на одной из которых и срабатывает хардварный бряк на запись по адресу 0x45EC5C:

image

Возможно, что-то идёт не так в процедуре 0x004597C8. Ставим софтварный бряк на её вызов (0x0045AD5B) и обнаруживаем, что его установка ведёт к тому, что на той системе, где всё работало нормально, теперь не срабатывает хардварный бряк на запись по адресу 0x45EC5C, в результате чего приложение падает, как и в случае другой системы. С хардварными бряками на выполнение всё обстоит так же.

Экспериментальным путём было выяснено, что установка бряков на нескольких предшествующих 0x0045AD5B инструкциях ведёт к такому же результату. Установить бряк с последующей нормальной работой приложения получилось лишь на данной инструкции:

image

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

Запускаем Trace into (Ctrl-F11), начиная с данной инструкции, и обнаруживаем, что различия в выполнении начинаются вот с этого места:

; если приложение выполняется в системе, где оно падает
004597EC Main     MOV EAX,DWORD PTR DS:[45EC98]             ; EAX=F2B47801
004597F1 Main     ADD EAX,64                                ; EAX=F2B47865
004597F4 Main     CDQ                                       ; EDX=FFFFFFFF
004597F5 Main     CMP EDX,DWORD PTR SS:[ESP+4]
004597F9 Main     JNZ SHORT Govorilk.00459807
00459807 Main     POP EDX                                   ; EDX=F2B47802

; если приложение выполняется в системе, где оно нормально работает
004597EC Main     MOV EAX,DWORD PTR DS:[45EC98]
004597F1 Main     ADD EAX,64                                ; EAX=064A1501
004597F4 Main     CDQ
004597F5 Main     CMP EDX,DWORD PTR SS:[ESP+4]
004597F9 Main     JNZ SHORT Govorilk.00459807
004597FB Main     CMP EAX,DWORD PTR SS:[ESP]


Отсюда заметно, что результат выполнения инструкции CMP EDX,DWORD PTR SS:[ESP+4] влияет на дальнейшую работу процедуры. В случае системы, где оно работает, флаг Z регистра флагов был выставлен на момент выполнения данной инструкции, в то время как в случае той системы, где приложение падает, данный флаг не был выставлен:

image

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

  • Вызывается функция GetTickCount, результат её вызова записывается в регистр EAX
  • Обнуляется содержимое регистра EDX
  • Содержимое регистров EDX (0x0) и EAX (результат выполнения функции GetTickCount) заносятся на стек
  • В регистр EAX помещается значение из адреса 0x45EC98
  • К нему прибавляется 0x64
  • Выполняется инструкция CDQ
  • И, наконец, сравнивается содержимое регистра EDX и значение по адресу ESP-4, где хранится предыдущее значение данного регистра, т.е. 0x0

Поставив хардварный бряк на запись по адресу 0x45EC98, можно увидеть, что туда также помещается результат выполнения функции GetTickCount:

image

Кстати, а почему GetTickCount возвращает такое большое значение? Ведь система выключалась мной ещё сегодня утром. На самом деле, тут играет свою роль hybrid boot и shutdown в Windows 8. Почитать об этом больше можно, например, тут.

Но вернёмся к основной теме нашего обсуждения. В чём же тут тогда проблема? Давайте внимательно посмотрим на содержимое регистров общего назначения в этом куске кода на обеих системах. Вам должно броситься в глаза, что после выполнения инструкции CDQ на системе, где приложение работает корректно, регистр EDX принимает значение 0x0, а в системе, где программа падает, 0xFFFFFFFF, что видно, например, на предыдущем скриншоте. Внимательно читаем описание инструкции CDQ:

Converts signed DWORD in EAX to a signed quad word in EDX:EAX by extending the high order bit of EAX throughout EDX


А теперь взглянем на сигнатуру функции GetTickCount:
DWORD WINAPI GetTickCount(void);


Как видите, она возвращает DWORD, который «раскрывается» в unsigned long, т.е. беззнаковый тип.

Уж не знаю, баг это разработчика или компилятора, но на такое поведение автор явно не рассчитывал, ведь в текущей ситуации говорилка будет падать на системах, которые работают дольше 24.8 и меньше 49.7 дней. Почему именно так? Нижняя граница определяется максимальным значением, которое может поместиться в знаковую 4-байтовую переменную — 2147483647:

2147483647 / 1000 / 60 / 60 / 24 = 24.8551348032

Верхняя же граница определяется самой функцией GetTickCount, о чём сказано в документации (собственно, она определяется максимальным значением, которое может поместиться в беззнаковую 4-байтовую переменную):

The elapsed time is stored as a DWORD value. Therefore, the time will wrap around to zero if the system is run continuously for 49.7 days


Ну что, пора исправить это допущение. Один из вариантов исправления проблемы — каким-то образом «подменить» вызов функции GetTickCount своим кодом, в котором мы будем «уменьшать» возвращаемое из неё значение настолько, чтобы оно поместилось в знаковую 4-байтовую переменную.

Вариантов решения данной задачи несколько, но давайте напишем code cave. Ищем свободное место (проще всего найти его в «конце» окна CPU) и помещаем туда следующий код:

; Сохраняем состояние регистров общего назначения на стеке
0045BAAA      60               PUSHAD
; Сохраняем состояние регистра флагов на стеке
0045BAAB      9C               PUSHFD
; Получаем значение EIP
0045BAAC      E8 00000000      CALL Govorilk.0045BAB1
0045BAB1      5E               POP ESI
; Вызываем функцию GetTickCount
0045BAB2      FF96 2F380000    CALL DWORD PTR DS:[ESI+382F]
; Сохраняем результат её выполнения на по адресу ESP-4
0045BAB8      894424 FC        MOV DWORD PTR SS:[ESP-4],EAX
; Восстанавливаем состояние регистра флагов
0045BABC      9D               POPFD
; Восстанавливаем состояние регистров общего назначения
0045BABD      61               POPAD
; Помещаем в регистр EAX результат выполнения функции GetTickCount
0045BABE      8B4424 D8        MOV EAX,DWORD PTR SS:[ESP-28]
; Выполняем операцию битового "AND" над значением, хранящемся в регистре EAX
0045BAC2      25 FFFFFF0F      AND EAX,0FFFFFFF
0045BAC7      C3               RETN


Значение 382F получилось в результате вычисления разницы между адресом в IAT, по которому хранится адрес функции GetTickCount (0x0045F2E0), и текущим адресом (0x0045BAB1).

Получить значение регистра EIP напрямую не получится — к нему нельзя обращаться ни на чтение, ни на запись.

Теперь переходим на место, где осуществляется JMP на функцию GetTickCount

image

и заменяем адрес, на который прыгает JMP, на адрес начала нашего code cave'а (в моём случае это 0x0045BAAA):

image

Сохраняем изменённый исполняемый файл (right-click по окну CPU -> Copy to executable -> All modifications -> Copy all -> right-click на появившемся окне -> Save file) и проверяем его работоспособность. Да, он действительно работает, однако при запуске говорилки теперь выдаётся знакомое практически всем пользователям Windows окно UAC. Почему именно это происходит можно почитать, например, тут, а для решения проблемы в нашем случае достаточно всего лишь дать нашему исполняемому файлу оригинальное название — «Govorilka_cp.exe».

Послесловие


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

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

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

PushAll — платформа для рассылки мгновенных уведомлений


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

Сама система позволяет легко подписываться и отписываться от тех или иных каналов. Что исключает возможность внезапного спама, как это происходит в случае с такими методами уведомлений как SMS и E-mail.


Сервис был запущен пол года назад. В его основе лежит авторизация через Google+ о которой я даже писал статью и GCM, через который я отправляю пуши как в Google Chrome, так и на Android приложение. Я собираюсь также написать реализацию на сокетах и засчет нее сделать поддержку всех браузеров, а также возможно устройств на Android без встроенного PUSH-сервиса.
Долгое время я использовал PushAll лишь для Self API — это API для отправки уведомлений самому себе. При помощи этой функции я получал информацию из автоматизированных скриптов использующихся на моей работе. Это очень удобно, в случе каких либо проблем — прямо на телефон и в хром прилетает уведомление о неполадках и сразу можно приступить к починке. Более того, система сохраняет историю уведомлений. Поэтому если вы спали, а у вас были неполадки, вы потом можете увидеть, что происходило и в каком порядке.

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

curl_setopt_array($ch = curl_init(), array(
CURLOPT_URL => "http://ift.tt/1IrdLyj",
CURLOPT_POSTFIELDS => array(
    "type" => "self",
    "id" => "<b>ВАШ ID</b>",
    "key" => "<b>ВАШ КЛЮЧ</b>",
    "text" => "Тестовое сообщение",
    "title" => "Заголовок"
  ),
  CURLOPT_SAFE_UPLOAD => true,
));
curl_exec($ch);
curl_close($ch);

Буквально месяц назад я ускорил темпы разработки и написал Broadcast API. Эта функция позволяет отправлять уведомления всем подписчикам созданного канала. Для отправки broadcast надо прописать ID канала, тип broadcast и ключ канала.
Ответ на все запросы API идет в формате JSON. На данный момент приходит параметр success с количеством устройсв, на которые был отправлен пуш, и error в случае ошибки. В ошибке может быть либо неправильный ключ, либо привышение лимитов.

Лимиты на данный момент такие:
— Не более 1 уведомления в 3 секунды для Self API (а куда чаще спамить на устройство)
— Не более 1 уведомления в 30 секунд для Broadcast — всем и так понятно, что спамить всему каналу чаще чем раз в 30 секунд будет неприятно для аудитории.
— Защита от дубликатов пары «Заголовок»-«Текст» раз в 10 минут.


Приблизительно 2 недели назад я начал искать партнеров. Я начал с студий озвучки сериалов. У них практически каждый день выходят серии, а зретелям необходимо оперативно узнавать, когда выходит озвучка их любимого сериала. За 2 недели я начал сотрудничать с BaibaKo, NewStudio и Jaskier Studio. За это время в моем сервисе зарегистрировалось около 1000 пользователей. В скором времени я реализую Unicast API для рассылки одному пользователю в канале и хочу начать сотрудничать с новостными сайтами. Эта функция позволит отправлять уведомления одному человеку из канала, к примеру это может быть ответ на комментарий, или личное сообщение.
Также хочу отметить недавно добавленную функцию фильтрации. Для сериалов она подошла идеально — можно выбрать нужные сериалы по ключевым словам, а также появляются необычные возможности. К примеру можно подписаться на сериалы, которые ты смотришь, и параллельно на все новые сериалы — надо лишь добавить S01E01 и любая первая сериая первого сезона любого сериала будет приходить. (зависит от формата нумерации серий)

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

Конкуренты

Из аналогов моему сервису можно выделить лишь два сервиса:
Pushover
— Платное приложение
— Отсутствие Русскоязычной локализации.
— Сервис скорее внешне больше для разработчиков чем для пользователей.
— Сильные ограничения рассылок. Дорогие тарифы

Pushbullet
— Гиковский сервис для обмена данными между устройствами
— Система каналов появилась недавно, что странно, через 2-3 месяца как я написал Self API и начал постить информацию о сервисе.
— Система каналов не предусматривает отправку одному пользователю
— Отсутствие Русскоязычной локализации.
— Отсутствуют фильтры.

Моей основной целью является создать сервис понятным пользователю. Вход и подписка в пару кликов (быстрая ссылка для подписки на канал новостей), простые приложения, все гиковские функции для разработчиков скрыты в специальный раздел. Также я хочу создать именно русскоязычное комьюнити разработчиков. Этот пост был написан первым в этом корпоративном блоге, дальше в нем будут публиковаться не только успехи, кейсы PushAll, но и различные проекты, в основу которых легла моя система уведомлений. К примеру почему бы не написать Push — клиент для социальной сети Facebook или Вконтакте? Пуши приходят через небольшое по весу приложение, а по клику на пуш ответить можно через веб-интерфейс.

Монетизация

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

Собственно адрес сайта: Pushall.ru
Для добавления канала можно перейти в раздел для разработчика. Отправлять уведомления можно как вручную, так и через API. То есть даже если вы не разработчик, вы все равно можете без проблем вести канал.

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

ExpvarMon — консольный мониторинг сервисов на Go

Для Go-программ существует удобнейший стандартный пакадж expvar, позволяющий одной строчкой подключить вывод дебаг информации в JSON-формате. И чтобы максимально быстро и наглядно мониторить текущее состояние, была написана консольная программа Expvarmon, требующая минимум конфигурации для вывода метрик и дебаг-информации для ваших Go-сервисов.

Функции:

  • single- и multi-services режимы
  • мониторинг локальных и удаленных программ
  • произвольное количество сервисов и переменных
  • поддержка значений для памяти, временных интервалов, bool и произвольных чисел/строк
  • sparkline-графики
  • отображение максимальных значений
  • отображение упавших/рестартовавших сервисов
  • авто-ресайз при изменении размеров шрифта или окна


Введение


В стандартной библиотеке Go есть очень полезный пакадж expvar, который позволяет одной строчкой добавить вывод дебаг информации в json-формате по адресу /debug/vars. По-умолчанию выводятся данные об использовании памяти и работе сборщика мусора (GC), и легко добавляются любые свои метрики/счетчики/переменные. Обычно эти данные собирает отдельный процесс, который кладет в какую-нибудь time-series базу данных, и затем это превращается в удобные и красивые дашборды.

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

Именно для таких случаев и была за пару выходных написана программа для мониторинга переменных expvar прямо в терминале, которая требует почти нулевую конфигурацию и не использует никаких сторонних баз и ресурсов. Программа использует отличнейший пакадж TermUI от любителя терминалов gizak (посмотрите его домашнюю страничку!).

Установка


Установка программы, как и любой другой программы на Go предельно проста:
go get http://ift.tt/1ALP3Cg

Надеюсь, $GOPATH/bin у вас прописан в $PATH.

Использование


expvar

Короткое объяснение
import _ "expvar"

Длинное объяснение

Если вы еще не знакомы с пакетом expvar, то краткое объяснение и инструкция.
В функции init() этого пакета написано следующее:
func init() {
        http.HandleFunc("/debug/vars", expvarHandler)
        Publish("cmdline", Func(cmdline))
        Publish("memstats", Func(memstats))
}

Первая строка регистрирует хендлер для обработки URL "/debug/vars" для стандартного http.DefaultServeMux из стандартного пакета net/http. Если вы пока не знаете, как устроен net/http, возможно это будет отдельной статьей, но сейчас вам достаточно знать, что если ваша программа стартанет стандартный http-сервер (скажем, http.ListenAndServe(":1234", nil)), то у нее автоматически появится обработчик GET-запроса по адресу /debug/vars. И ответ этого запроса будет по умолчанию содержать примерно следующий JSON:
$ curl -s http://localhost:1234/debug/vars | json_pp | head
{
   "cmdline" : [
      "./expvar.demo",
      "-bind=:1234",
   ],
   "memstats" : {
      "NumGC" : 5,
      "Alloc" : 114016,
      "DebugGC" : false,
      "HeapObjects" : 519,
      "HeapSys" : 868352,
      "StackInuse" : 180224,


Это JSON-репрезентация двух переменных, определенных следующими двумя строчками — командная строка и текущие значения runtime.Memstats. Последняя содержит массу подробностей про текущее использование памяти и работу сборщика мусора, большая часть из которых ну уж слишком подробная. Обычно для мониторинга используются значения Alloc, Sys, HeapAlloc — реально используемая память, запрошенная у OS, используемая память в куче соответственно.

Поскольку init() вызывает автоматически при импорте пакаджа, в программе достаточно добавить одну строчку:

import _ "expvar"

expvarmon

Данная же программа предельно проста — она с указанным интервалом вычитывает этот JSON для указанного сервиса или сервисов, и отображает его в удобном для мониторинга виде, при этом показывает sparkline-графики для числовых значений. Все что ей нужно для запуска, это порт или «хост: порт» сервиса(-ов) которые вы хотите мониторить. К примеру:
expvarmon -ports="80"
expvarmon -ports="23000-23010,80"
expvarmon -ports="80,remoteapp.corp.local:80-82"


Можно указывать как одну, так и 30+ портов/сервисов — на сколько у вас хватит размеров терминала.
Программа может также мониторить саму себя:
expvarmon -self

Интервал по умолчанию — 5 секунд, но можно указать меньше или больше. Имейте ввиду, что слишком короткий интервал не рекомендован, так как даже обновление memstats влияет на сборщик мусора и увеличивает паузы. Если ваша аппликация бежит под хорошой нагрузкой, сильно короткий интервал (100ms) может повлиять на продуктивность.
expvarmon -self -i 5m
expvarmon -self -i 30s

По умолчанию мониторятся следующие переменные:
  • mem:memstats.Alloc
  • mem:memstats.Sys
  • mem:memstats.HeapAlloc
  • mem:memstats.HeapInuse
  • memstats.EnableGC
  • memstats.NumGC
  • duration:memstats.PauseTotalNs

Значения переменных берутся в том виде, в каком они есть в JSON, с записью через точку. Модификаторы «mem:», «duration:», «str:» — опциональны, и влияют на форматирование/отображение. Если указывать переменные без модификатора, то они будут отображаться как есть. Модификатор «mem:» будет конвертировать значения в «KB, MB, etc» представление, а «duration:» — конвертировать int64 значение в time.Duration(«ns, ms, s, m, etc»). Модификатор «str:» просто говорит, что значение не цифровое, и sparkline-график для этой переменной рисовать не нужно.

К примеру:

expvarmon -ports="80" -vars="mem:memstats.Alloc,duration:Response.Mean,Goroutines,str:Uptime"

Опять же, можно указать как одну переменную, так и пару десятков, насколько у вас хватит размера терминала.

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

Дополнительно


Expvarmon отображает иконками сервисы, которые падали и которые лежат в данный момент. К сожалению, если интервал опроса больше, чем время падения/рестарта сервиса, то падение сервиса программа не словит.

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

Благодаря возможностям TermUI, программа динамически меняет размеры всех виджетов при изменении размера шрифта или окна терминала.

Дополнительные переменные


Лично мне в стандартном перечне переменных expvar не хватает двух вещей — количества запущенных горутин и аптайм сервиса. Вот демо-враппер, который экспортит три дополнительные переменные. Просто подключаете его в свою программу, одним импортом.
package myexpvars

import (
        "expvar"
        "math/rand"
        "runtime"
        "time"
)

var (
        startTime = time.Now().UTC()
)

// goroutines is an expvar.Func compliant wrapper for runtime.NumGoroutine function.
func goroutines() interface{} {
        return runtime.NumGoroutine()
}

// uptime is an expvar.Func compliant wrapper for uptime info.
func uptime() interface{} {
        uptime := time.Since(startTime)
        return int64(uptime)
}

// responseTime fake var.
func responseTime() interface{} {
        resp := time.Duration(rand.Intn(1000)) * time.Millisecond
        return int64(resp)
}

func init() {
        expvar.Publish("Goroutines", expvar.Func(goroutines))
        expvar.Publish("Uptime", expvar.Func(uptime))
        expvar.Publish("MeanResponse", expvar.Func(responseTime))
}


Что делать, если используется сторонний http-роутер, вместо стандартного


Многие веб-сервисы на Go пишутся с использованием дополнительных веб-фреймворков или более продвинутых http-роутеров. expvar из коробки с ними работать не будет. Для него вам нужно будет таки стартануть стандартный http.ListenAndServer() на другом порту. А это даже лучше, так как открывать наружу /debug/vars крайне не рекомендуется, если речь идет о публичных веб-сервисах.

Если же вы используете стандартный net/http, но хотите, чтобы expvar был на другом порту, проще всего сделать так. «Основной» ServeMux запустить следующим образом:

mux := http.NewServerMux()
server := &http.Server{Addr: “:80”, Handler: mux}
server.ListenAndServe()

а /debug/vars повесить на стандартный
http.ListenAndServe(":1234", nil)

Скриншоты


Ссылки


Github: http://ift.tt/1zSbBGC
Expvar docs: http://ift.tt/1DiddrQ
runtime.Memstats: http://ift.tt/193raOC

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

пятница, 8 мая 2015 г.

UMTS в походных условиях и самодельная антенна на 2ГГц

Для чего я это делаю

Иногда мне приходится выезжать далеко за город и оставаться там несколько дней. Да тут вот и окончательно переезжать поближе к природе задумал к осени. Интернета не то чтобы хочется, он просто необходим для работы. Покрытие сетью 3G в отдалённых районах нашей области постепенно улучшается, но до идеала всё ещё как до Китая очень далеко. Встроенные антенны USB-«свистков» работают категорически неудовлетворительно. Потому я начал по привычке «колхозить»…

Краткий обзор ситуации с 3G

Подавляющее большинство готовых 3G-устройств работают только со своей встроенной антенной и никаких возможностей подключить внешнюю антенну не имеют. Тех, которые таковую имеют, во-первых, днём с огнём вечером разогнём не сыщешь, во-вторых, это подключение в подавляющем большинстве случаев ненадёжно и имеет плохие характеристики в плане ослабления/отражения сигнала и внесения дополнительных шумов. Название pigtail («поросячий хвостик»), принятое в отношении таких кабелей-переходников, очень точно описывает эту ситуацию.
Качество встроенных антенн приемлемо для городской среды, где БСки натыканы через шаг. Для походных же условий такая условно-всенаправленная супер-укороченная антенна не годится совершенно.
Однако многие 3G-модемы, даже не имеющие видимого разъёма для подключения внешней антенны, имеют точку подключения для антенны на своей плате, видимо, для диагностики. Зачастую на этой точке даже распаян разъём.
Без «хирургического вмешательства», таким образом, не обойтись.

Под катом большие картинки.

«Хирургическое вмешательство» в 3G-модем

Модем может быть каким угодно. Мой выбор пал на Huawei E1550, ибо это старьё подвернулось мне у гопника в подворотне ныне можно купить с рук рублей за 100 — испортить не жалко. Подробности его модификации прекрасно находятся в интернете любой популярной поисковой системой. [напримеръ]
В общем же случае надо:

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

Так как контакты точки подключения расположены очень близко, популярный кабель вроде РК-50 или RG-58, конечно же, не подойдёт. Я лично выкусил кусочек такого кабеля из списанного в утиль нерабочего ноутбука — там он соединял модуль Wi-Fi с антенной. Его диаметр не превышает 2мм, что отлично подходит для таких манипуляций с модемом.

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

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

Почему антенна Харченко?

Диполь или GP здесь слабоваты. Волновой канал и логопериодическая помимо сложности ручного изготовления на таких частотах и, как следствие, «хрупкости», имеют очень узкий главный лепесток диаграммы направленности. В походных условиях точно «прицелиться» на базовую станцию (БС) и надёжно зафиксировать такую антенну очень сложно.
Компромисс был найден в виде зигзагообразной антенны Харченко (о которой я упоминал в одной из предыдущих статей — вот и настала пора её попробовать). Её главный лепесток диаграммы достаточно широк, чтобы не очень мучаться с нацеливанием на БС, при этом антенна достаточно проста в изготовлении и имеет относительно неплохой коэффициент усиления (6..9dB).

Модификация антенны

Антенна Харченко достаточно широкополосна, но при этом группы частот передачи «от МТ к БС» (1885..2025 МГц) и «от БС к МТ» (2110..2200 МГц) в стандарте UMTS разнесены достаточно далеко. Как я уже упоминал в комментариях к предыдущей статье, у БС практически всегда хватит мощности «докричаться» до мобильного терминала, но у мобильного терминала (МТ) мощности маловато (особенно у USB-«свистка»). Можно, конечно, изготовить антенну, оптимально работающую в группе частот передачи «от МТ к БС», и довольствоваться средней паршивости приёмом, но мне захотелось выжать из этой технологии максимум.
Я узнал, что антенну Харченко можно сделать ещё более широкополосной, включив несколько вибраторов антенны параллельно в одной плоскости в точке «запитки». Однако рассчитанные размеры этих вибраторов для частотных групп UMTS в части «короткого плеча» различались менее чем на полмиллиметра при требуемой ширине проводника 0.8-0.9мм. То есть разнести «короткие плечи» при сохранении плоскости вибратора мне не представлялось возможным.
И я решил: сделаю «не по правилам» и посмотрю, что получится. Отсюда и некоторая небрежность при изготовлении первых вариантов антенны: я просто не верил, что это «взлетит».

Первый начерченный на бумаге вариант вибратора выглядел так:

Для «малого» контура центральная частота была выбрана равной 2145 МГц, для «большого» — 2010 МГц.

Данные расчётов
Использовался этот онлайн-калькулятор

Антенна Харченко (зигзагообразная)
— Центральная частота f: 2010 МГц
Волновое сопротивление антенны ρ: 50 Ом
Полоса пропускания при КСВ < 1.5: 126 МГц
Полоса пропускания при КСВ < 2: 234 МГц
— Длина волны λ: 149.25 мм
Размер L1 (внешняя сторона квадрата): 38.5 мм
Размер L2 (внутренняя сторона квадрата): 34.3 мм
Размер L3 (длина вибратора): 125 мм
Размер L4 (промежуток в месте подключения): 7.7 мм
Размер B (ширина рефлектора): 85 мм
Размер H (длина рефлектора): 146 мм
Диаметр проволоки: 0.9 мм
Общая длина проволоки: 303 мм
Расстояние вибратор-рефлектор D: 23.1 мм
Крайний угол ромба α: 65°

Антенна Харченко (зигзагообразная)
— Центральная частота f: 2145 МГц
Волновое сопротивление антенны ρ: 50 Ом
Полоса пропускания при КСВ < 1.5: 134 МГц
Полоса пропускания при КСВ < 2: 250 МГц
— Длина волны λ: 139.86 мм
Размер L1 (внешняя сторона квадрата): 36.1 мм
Размер L2 (внутренняя сторона квадрата): 32.1 мм
Размер L3 (длина вибратора): 117.2 мм
Размер L4 (промежуток в месте подключения): 7.2 мм
Размер B (ширина рефлектора): 80 мм
Размер H (длина рефлектора): 137 мм
Диаметр проволоки: 0.8 мм
Общая длина проволоки: 283 мм
Расстояние вибратор-рефлектор D: 21.7 мм
Крайний угол ромба α: 65°

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

Изготовление пробной версии

Построение контуров вибратора антенны было повторено на текстолите:

Это вызовет священный ужас у многих студентов:

Впрочем, я тоже давненько не работал стеклянным рейсфедером, потому опущу промежуточные стадии: ;)

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

Впервые попробовал метод травления меди в растворе состава «Лимонная кислота+Поваренная соль+Перекись водорода». Сначала были сложности, связанные с некачественным (отсыревшим) гидроперитом. В чём было дело, я понял, бросив в раствор пару таблеток из новой пачки (отсыревшие таблетки гидроперита распухшие, рассыпавшиеся, зернистые — толку от них нет; кошерные таблетки плотные, имеют чёткие формы и не рассыпаются в руках). Травится медь и в самом деле быстро, аккуратно и экстремально дёшево.
Задумался теперь, куда девать несколько килограммов хлорного железа, купленного некогда «про запас»…

Готовый вибратор после лужения:

Припаиваю. Да-да, ножки — из мягкого пенопласта. Повторяю: я не верил, что это «взлетит» и оставлял пространство для манёвров в виде «регулируемого» расстояния между вибратором и рефлектором.

В сборе. USB-разъём приляпан двусторонним скотчем к рефлектору. Рефлектор вставлен в прорезь половины пластиковой бутылки — чтоб стояло без рук.

Результаты испытаний пробной версии

Чуть не выкинул всё в мусорку не разобравшись

Когда я включил модем в USB-порт, с ожидаемым прискорбием увидел «два зелёных свистка» (владельцы подобных девайсов поймут). Всё, думаю, модем капут («kaput» — «испорчен», «сломан» на немецком). Но на всякий случай запустил minicom и ввёл «AT+CSQ» (должно показать RSSI в попугаях). Ожидал нулей. Но модем ответил: "+CME ERROR: 13". Загуглил. Проблема оказалась в SIM-карте, была устранена и модем бодро замигал синеньким.

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

Speedtest неоднозначен, но это, судя по всему, проблема не радиоканала (тесты повторялись неоднократно, у каждого тест-сервера были свои «тараканы»):


(хорошая скорость, плохой пинг)


(не очень скорость, хороший пинг)

Ну хорошо, это город. На гвоздь ловить можно, как говорили когда-то. Я направлял эту антенну в пол и даже так ловил какие-то отголоски сигнала.

Но вот настало время выехать за город. Нахожусь в деревянном срубе с металлической крышей. Телефоны ловят так себе, интернет — на скоростях GPRS. Ловлю ближайшую БС…

Казалось бы, негусто. Но интернет есть, и он UMTS (да и попробовал бы он быть иным с такой-то антенной!) Попробуем-ка Speedtest…


Вот так! Практически мегабит!

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

Подготовка к походным условиям


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

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

Для начала я ввёл USB-кабель внутрь контейнера (пришлось его разрезать и спаять заново, чтобы не делать огромных размеров дыру):

Затем я отложил паяльник и взял один из своих любимых инструментов: клей «горячие китайские сопли» клеевой пистолет. Герметизируем ввод кабеля:

Закрепляем антенну внутри, прихватываем капельками клея критичные места, чтобы ничего не болталось при переноске:

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

После всех этих экзерсисов антенна, разумеется, работает по-прежнему, ведь в радиочастотной части ничего не поменялось, а USB — это «настоящая бездушная цифрА», которую удлинять и перепаивать гораздо проще и спокойнее. Следующим этапом будет попытка поднять её на чердак или на мачту, где висит сейчас телеантенна, но сегодня вечер пятницы и мне лень этим заниматься, тем более в такую погоду. Лучше включу его, залью оставшиеся фотографии девайса и допишу этут статейку. ;)

(неуловимо напоминает какой-то фирменный роутер)

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

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