...

суббота, 13 ноября 2021 г.

Что полезно знать Java-разработчику про вывод типов

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

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

Comparator<String> c1 = Comparator.comparing(String::length).reversed();
Comparator<String> c2 = Comparator.comparing(s -> s.length()).reversed();
Comparator<String> c3 = Collections.reverseOrder(Comparator.comparing(s -> s.length()));

Первое важное знание: в Java есть два типа выражений (JLS §15.2). Первый тип — «автономные выражения» (standalone expression), а второй — «поли-выражения» (poly expression). Тип автономных выражений вычисляется, глядя исключительно на само выражение. Если выражение автономное, совершенно неважно, в каком оно встретилось контексте, то есть что вокруг этого выражения. Для поли-выражений контекст важен и может влиять на их тип. Если поли-выражение вложено в другое поли-выражение, то фактически выбирается самое внешнее из них, и для него запускается процесс вывода типов. По всем вложенным поли-выражениям собираются ограничения (constraints). Иногда к ним добавляется целевой тип. Например, если поли-выражение — это инициализатор переменной, то тип этой переменной является целевым и тоже включается в ограничения. После этого выполняется редукция ограничений и определяются типы для всех поли-выражений сразу. Скажем, простой пример:

Comparator<String> c2 = Comparator.comparing(s -> s.length());

Здесь лямбда является поли-выражением. Вообще лямбды и ссылки на методы всегда являются поли-выражениями, потому что их нужно отобразить на какой-то функциональный интерфейс, а по содержимому лямбды вы никогда не поймёте, на какой. Вызов метода Comparator.comparing тоже является поли-выражением (ниже мы поймём, почему). У лямбды надо определить точный функциональный тип, а у Comparator.comparing — значения типовых параметров T и U. В процессе вывода устанавливается, что


  • T = String
  • U = Integer
  • Тип лямбды = Function<String, Integer>
  • Тип параметра s = String

Только некоторые выражения в Java могут быть поли-выражениями. Вот их полный список (на момент Java 17):


  • Выражения в скобках
  • Создание нового объекта (new)
  • Вызов метода
  • Условные выражения (?:)
  • switch-выражения (те что в Java 14 появились)
  • Ссылки на методы
  • Лямбды

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

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


  • Контекст присваивания (assignment context) — это контекст, при котором автоматически выполняется преобразование присваивания. Включает в себя инициализацию переменной (кроме переменной с неявным типом var), оператор присваивания, а также возврат значения из метода или лямбды (как с использованием return, так и без).
  • Контекст вызова (invocation context) — аргумент вызова метода или конструктора.
  • Контекст приведения (cast context) — аргумент оператора приведения типа.

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

Runnable r1 = () -> {}; // можно
Runnable r2 = true ? () -> {} : () -> {}; // можно
Object r3 = (Runnable)() -> {}; // можно
Object r4 = (Runnable)(true ? () -> {} : () -> {}); // нельзя!
Object r5 = true ? (Runnable)() -> {} : (Runnable)() -> {}; // можно

Условный оператор во второй строке является поли-выражением, потому что он в контексте присваивания. Поэтому он может посмотреть наружу и увидеть, что результат должен быть типа Runnable, а значить использовать эту информацию для вывода типов веток и в итоге присвоить обеим лямбдам тип Runnable. Однако четвёртая строчка в таком виде не работает, несмотря на большое сходство. Здесь условный оператор true ? () -> {} : () -> {} находится в контексте приведения, что по спецификации делает его автономным выражением. Поэтому мы не можем выглянуть за его пределы и увидеть тип Runnable, а значит мы не знаем, какой тип назначить лямбдам — возникает ошибка компиляции. В этом случае придётся переносить приведение типов в каждую ветку условного оператора (или не писать такой код вообще).

Не только контекст, но и вид выражения может влиять на полистость. Например, выражение new может быть поли-выражением (в соответствующем контексте), только если используется оператор «ромб» (new X<>(), JLS §15.9). В противном случае тип результата всё равно однозначен и нет смысла усложнять компиляцию. Аналогичная мысль применяется к выражениям вызова метода, только это приводит к более сложным условиям (JLS §15.12):


  • Мы вызываем generic-метод
  • Этот generic-метод упоминает хотя бы один из своих типовых параметров в возвращаемом типе
  • Типы-аргументы не заданы явно при вызове в <угловых скобках>

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

Интересная история с условным оператором (JLS §15.25). Сначала в зависимости от типов выражений в ветках выясняется разновидность оператора: это может быть булев условный оператор, числовой условный оператор или ссылочный условный оператор. Только ссылочный условный оператор может быть поли-выражением, а булев и числовой всегда автономные. С этим связано много странностей. Вот например:

static Double get() {
  return null;
}

public static void main(String[] args) {
  Double x = true ? get() : 1.0;
  System.out.println(x);
}

Здесь типы веток условного оператора — конкретно Double и конкретно double. Это означает, что условный оператор числовой (numeric conditional expression, JLS §15.25.2), то есть автономный. Соответственно, мы не смотрим наружу, нас не волнует, что мы присваиваем результат в объектный Double. Мы определяем тип только по самому оператору, и этот тип — примитивный double. Соответственно, для балансировки типов добавляется unboxing левой ветки, а потом для присваивания добавляется снова boxing:

Double x = Double.valueOf(true ? get().doubleValue() : 1.0);

Здесь мы разворачиваем результат метода get(), а потом заново сворачиваем. Разумеется, этот код падает с NullPointerException, хотя казалось бы мог бы и не падать.

Ситуация в корне меняется, если мы объявим метод get() по-другому:

static <T> T get() { ... }

Теперь в одной из веток не числовой тип Double, а неизвестный ссылочный тип T. Весь условный оператор становится ссылочным (reference conditional expression, JLS §15.25.3), соответственно становится поли-выражением и может посмотреть наружу, на целевой тип Double и использовать именно его как целевой тип веток. В итоге обе ветки успешно приводятся к типу Double, для чего добавляется boxing в правой ветке:

Double x = true ? get() : Double.valueOf(1.0);

Теперь программа успешно печатает null и завершается. Такие нестыковки обусловлены историческими причинами и необходимостью совместимости. В первых версиях Java никаких поли-выражений не было, все были автономными, поэтому надо было выкручиваться, и выкручивались не всегда идеально. К счастью, это не распространяются на более новые switch-выражения. Для них нет дополнительных условий на полистость кроме контекста, поэтому такой код вполне ожидаемо печатает null вместо падения с исключением:

static Double get() {
    return null;
}

public static void main(String[] args) {
    Double x = switch (0) {
        case 0 -> get();
        default -> 1.0;
    };
    System.out.println(x);
}

Вернёмся к нашему примеру с компараторами. Я раскрою карты: второй вариант не компилируется.

Comparator<String> c1 = Comparator.comparing(String::length).reversed(); // можно
Comparator<String> c2 = Comparator.comparing(s -> s.length()).reversed(); // нельзя
Comparator<String> c3 = Collections.reverseOrder(Comparator.comparing(s -> s.length())); // можно

Вот главное, что следует запомнить:


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

В первых двух строчках у нас есть квалификаторы: Comparator.comparing(String::length) и Comparator.comparing(s -> s.length()). При определении типа квалификатора мы не можем смотреть на то что происходит вокруг, нам остаётся пользоваться только самим содержимым квалификатора.

Comparator.comparing возвращает Comparator<T>, принимая функцию Function<? super T, ? extends U>, и нам необходимо определить значения T и U. В случае со ссылкой на метод у нас есть дополнительная информация: ссылка однозначно указывает на метод length() в классе String. соответственно, выводу типов хватает этого, чтобы понять, что T = String и U = Integer. Однако в случае с лямбдой у нас нет никаких указаний на то что s — это строка. Соответственно, у нас нет ограничений на T, а значит в соответствии с правилами редукции выбирается максимально общий тип: T = Object. Далее запускается анализ тела лямбды и мы обнаруживаем, что у класса Object нет метода length(), из-за чего компиляция останавливается. Вот такое, кстати, бы сработало, потому что hashCode() в объекте есть:

Comparator<Object> cmp = Comparator.comparing(s -> s.hashCode()).reversed();

Понятно и почему работает строчка c3. Так как Comparator.comparing здесь в контексте вызова, мы можем подняться наверх и добраться до контекста присваивания, а значит, использовать целевой тип Comparator<String>. Тут вывод сложнее, потому что есть ещё переменная типа в методе reverseOrder. Тем не менее компилятор справляется и успешно всё выводит.

Как починить c2, если всё-таки хочется использовать квалификатор? Мы уже знаем достаточно, чтобы понять, что вот это не сработает:

Comparator<String> c2 = 
  ((Comparator<String>)Comparator.comparing(s -> s.length())).reversed();

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

Comparator<String> c2 = 
  Comparator.comparing((Function<String, Integer>) s -> s.length()).reversed();

Вариант проще: сделать вызов метода автономным. Для этого надо добавить типы-аргументы. В итоге тип вызова comparing устанавливается однозначно, и из него уже выводится тип лямбды:

Comparator<String> c2 = 
  Comparator.<String, Integer>comparing(s -> s.length()).reversed();

Ещё проще в данном случае — явно указать тип параметра лямбды. Тут у нас вызов comparing по-прежнему является поли-выражением, но появляется ограничение на тип s, и его хватает, чтобы вывести всё остальное правильно:

Comparator<String> c2 = 
  Comparator.comparing((String s) -> s.length()).reversed();

Можно ли было распространить вывод типов на квалификаторы, чтобы c2 работало без дополнительных подсказок компилятору? Возможно. Но, как я уже говорил, процедура вывода типов и так невообразимо сложная. В ней и так до сих пор есть тёмные места, а даже когда она правильно работает, она может работать ужасно долго. К примеру, возможно написать относительно несложный код, который создаст сотню ограничений и поставит на колени и IDE, и компилятор javac, потому что реализация вывода типов может быть полиномом довольно высокой степени от количества ограничений. Если мы в этот замес добавим квалификаторы, всё станет сложнее на порядок, ведь они будут интерферировать со всем остальным. Также возникнут проблемы из-за того, что мы можем вообще толком не знать, какой метод какого класса мы пытаемся вызвать. Например:

<T> T fn(UnaryOperator<T> op) {
  return op.apply((T) " hello "); // грязно, но имеем право!
}

String s = fn(t -> t).trim();

Если мы выводим тип квалификатора fn(t -> t) вместе с типом всего выражения, то мы даже не знаем, у какого класса вызывается метод trim(). Нам подходит любой метод trim() в любом классе, который не принимает аргументов и возвращает строку. Например, метод String.trim() подойдёт. Или ещё какой-нибудь. У этого уравнения может быть много решений. Придётся как-то отдельно обговаривать в спецификации такие случаи. Так или иначе, я не был бы счастлив заниматься поддержкой данной возможности в IDE.

Adblock test (Why?)

CNN: в Нидерландах задержали хакера из России

Американский телеканал CNN сообщает о задержании в Нидерландах 29-летнего россиянина Дениса Дубникова по запросу ФБР. Министерство юстиции США требует его экстрадиции в Соединенные Штаты, поскольку Дубников подозревается в отмывании денег, полученных в результате хакерского вымогательства. 

Минюст подчеркивает, что россиянин незаконно завладел $40 тыс. в криптовалюте.

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

Правоохранительные органы США задержали россиянина в рамках расследования против фактов использования программы-вымогателя Ryuk. При помощи вредоносного ПО была осуществлена серия атак на американские больницы, в результате которых были изменены даты приема пациентов на химиотерапию и маммографию.

В 2020 году федеральные органы Соединенных Штатов предупредили об опасности взлома больниц и поставщиков медицинских услуг при помощи Ryuk. В Минюсте отказались комментировать дело Дубникова.

Задержание россиянина является частью кампании правоохранительных органов США и Европы по борьбе с восточноевропейскими и российскими хакерскими группами, которые вымогали у бизнеса миллионы долларов. В ноябре Минюст заявил о задержании гражданина Украины Ярослава Васинского, которого обвиняют в использовании вымогательского ПО REvil против американской компании Kaseya. 

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

Участник расследований атак при помощи Ryuk старший вице-президент и технический директор Mandiant (бывшая FireEye) Чарльз Кармакал заявил о том, что врачи и люди, осуществляющий уход за больными, были лишены доступа к системам, которые необходимы для оказания помощи пациентам. Многие организации были вынуждены платить злоумышленникам, поскольку опасались возможных негативных последствий для жизни людей.

В конце октября перед судом в США предстал гражданин России Владимир Дунаев, которого американские власти обвинили в участии в преступной организации, а также создании и применении программы-вымогателя Trickbot. Ранее по запросу Минюста Соединенных Штатов хакера экстрадировали из Южной Кореи. Дунаеву грозит до 60 лет лишения свободы.

Adblock test (Why?)

Новое лекарство восстановило серьёзное повреждение спинного мозга у мыши

Исследователи из Северо-Западного университета разработали новое лекарство, инъекция которого позволила восстановить ткани после серьёзного повреждения спинного мозга у мыши и излечить паралич задних конечностей. Работа опубликована в журнале Science.

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

Мышь снова смогла двигать задними конечностями уже через четыре недели после ввода лекарства. Подействовав, составляющие лекарства (синтетические молекулы, имитирующие белки) полностью рассосались в теле мыши в течение 12 недель, не оставив видимых побочных эффектов. Эти составляющие распались на питательные вещества, поглощаемые клетками.
Вёл исследование Сэмюел Стапп, основатель и директор Иститута бионанотехнологий Симпсона Куэрри (SQI) и его Исследовательского центра регенеративной наномедицины. По его словам, новое исследование проведено в рамках поиска терапии, способной избавить людей от паралича конечностей после серьёзных травм или заболеваний. Учёные давно уже бьются над этой сложной задачей – ведь у центральной нервной системы нет возможности активно восстанавливаться после получения подобных значительных повреждений. Учёные института уже готовят заявку в Управление по санитарному надзору за качеством пищевых продуктов и медикаментов США с тем, чтобы получить разрешение на использование лекарства на людях.

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

Стапп поясняет, что рецепторы в нейронах постоянно двигаются, и ключевая инновация новой терапии состоит в том, чтобы управлять совместным движением более 100 000 молекул, из которых состоят упомянутые нановолокна. Молекулы двигаются, «танцуют» и иногда даже временно выпрыгивают из своих структур, супрамолекулярных полимеров, в результате чего им удаётся эффективнее связываться с рецепторами.

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

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

Adblock test (Why?)

[Перевод] Звёздный рейтинг: решение с использованием SVG

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

  • Производительность (без использования картинок)

  • Адаптивность под разный размер

  • Доступность

  • Частичное заполнение звёзд (например, 3.5 или 3.2)

  • Легкая поддержка с помощью CSS

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

Дорогой читатель, если ты ищешь другие способы решения данной задачи (не только с помощью SVG), рекомендую прочитать на сайте CSS Tricks статью "Five Methods for Five-Star Ratings" автора Alfred Genkin.

Вступление

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

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

Базовая разметка

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

<svg width="32" height="32" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
    <path d="M31.547 12a.848.848 0 00-.677-.577l-9.427-1.376-4.224-8.532a.847.847 0 00-1.516 0l-4.218 8.534-9.427 1.355a.847.847 0 00-.467 1.467l6.823 6.664-1.612 9.375a.847.847 0 001.23.893l8.428-4.434 8.432 4.432a.847.847 0 001.229-.894l-1.615-9.373 6.822-6.665a.845.845 0 00.214-.869z" />
</svg>

В браузере данный код отрисует чёрную звезду с шириной и высотой, равными 32px.

Доступность

Помните, что для пользователей скринридеров в атрибуте aria-label нужно будет представить рейтинг не в виде изображения, а в виде текста.

<p aria-label="Rating is 4.5 out of 5">
   <svg width="32" height="32" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
      <path d="M31.547 12a.848.848 0 00-.677-.577l-9.427-1.376-4.224-8.532a.847.847 0 00-1.516 0l-4.218 8.534-9.427 1.355a.847.847 0 00-.467 1.467l6.823 6.664-1.612 9.375a.847.847 0 001.23.893l8.428-4.434 8.432 4.432a.847.847 0 001.229-.894l-1.615-9.373 6.822-6.665a.845.845 0 00.214-.869z" />
   </svg>
</p>

Как использовать SVG повторно

Мы можем либо просто пять раз скопировать приведённую выше разметку, либо сохранить SVG-код рисования фигуры "path" в виде шаблона и использовать повторно без дублирования разметки. Давайте сделаем это.

Сначала нужно создать SVG с нулевой шириной и высотой, чтобы он не занимал место

<svg width="0" height="0" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <!-- Content -->
</svg>

В этом SVG в теге <symbol> нужно задать SVG-код рисования фигуры "path", который нарисует звезду.

Согласно MDN:

Элемент symbol используется для определения шаблона графических объектов, экземпляры которых потом можно многократно отрисовывать с помощью элемента <use>

В элементе <symbol>, кроме кода, который отрисует иконку звезды, важно добавить атрибут id, чтобы на него можно было сослаться позже

<svg width="0" height="0" xmlns="http://www.w3.org/2000/svg">
    <symbol xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" id="star">
        <path d="M31.547 12a.848.848 0 00-.677-.577l-9.427-1.376-4.224-8.532a.847.847 0 00-1.516 0l-4.218 8.534-9.427 1.355a.847.847 0 00-.467 1.467l6.823 6.664-1.612 9.375a.847.847 0 001.23.893l8.428-4.434 8.432 4.432a.847.847 0 001.229-.894l-1.615-9.373 6.822-6.665a.845.845 0 00.214-.869z" />
    </symbol>
</svg>

После всех этих действий мы сможем переиспользовать символ звезды с помощью элемента <use> . Идея заключается в том, чтобы в атрибуте xlink:href ссылаться на id созданного символа.

<p class="c-rate">
    <svg class="c-icon" width="32" height="32">
      <use xlink:href="#star"></use>
    </svg>
    <svg class="c-icon" width="32" height="32">
      <use xlink:href="#star"></use>
    </svg>
    <svg class="c-icon" width="32" height="32">
      <use xlink:href="#star"></use>
    </svg>
    <svg class="c-icon" width="32" height="32">
      <use xlink:href="#star"></use>
    </svg>
    <svg class="c-icon" width="32" height="32">
      <use xlink:href="#star"></use>
    </svg>
</p>

Стилизация звезды

Теперь, когда мы получили список звёзд, давайте рассмотрим CSS-стилизацию. Я определил для звезды жёлтый и серый цвета.

<p class="c-rate">
    <svg class="c-icon active" width="32" height="32">
      <use xlink:href="#star"></use>
    </svg>
    <svg class="c-icon active" width="32" height="32">
      <use xlink:href="#star"></use>
    </svg>
    <svg class="c-icon active" width="32" height="32">
      <use xlink:href="#star"></use>
    </svg>
    <svg class="c-icon active" width="32" height="32">
      <use xlink:href="#star"></use>
    </svg>
    <svg class="c-icon" width="32" height="32">
      <use xlink:href="#star"></use>
    </svg>
</p>
.c-icon {
    --star-active: #fece3c;
    --star-inactive: #6c6962;
    fill: var(--star-inactive);
}

.c-icon.active {
    fill: var(--star-active);
}

Приведённые разметка и стили дадут следующий результат

Частичное заполнение

Использование SVG даёт нам две отличных возможности. Первая — использование SVG-масок, вторая — использование SVG-градиентов.

Половина звезды с помощью SVG-маски

Суть использования масок заключается в использовании тега <mask>, задающего область, внутри которой фигура остаётся видимой, а за пределами — обрезается.

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

Чтобы реализовать это с помощью SVG, нужно сделать следующее:

  1. Создать SVG-шаблон, который можно повторно использовать

  2. Добавить элемент <mask> в виде прямоугольник, расположенного на оси x, и спозиционированном на 50%

  3. Применить маску к фигуре звезды

<!-- Переиспользуемый SVG-шаблон -->
<svg width="0" height="0" viewBox="0 0 32 32">
  <defs>
    <!-- Маска, которая будет закрывать часть звезды -->
    <mask id="half">
      <rect x="50%" y="0" width="32" height="32" fill="white" />
    </mask>
    <!-- Фигура звезды -->
    <symbol id="star" viewBox="0 0 32 32">
      <path d="M31.547 12a.848.848 0 00-.677-.577l-9.427-1.376-4.224-8.532a.847.847 0 00-1.516 0l-4.218 8.534-9.427 1.355a.847.847 0 00-.467 1.467l6.823 6.664-1.612 9.375a.847.847 0 001.23.893l8.428-4.434 8.432 4.432a.847.847 0 001.229-.894l-1.615-9.373 6.822-6.665a.845.845 0 00.214-.869z" />
    </symbol>
  </defs>
</svg>

<!-- Пример применения звезды с наложением маски -->
<svg class="c-icon" width="32" height="32" viewBox="0 0 32 32">
  <use xlink:href="#star" mask="url(#half)"/>
</svg>

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

Далее встаёт вопрос о том, как мы можем с помощью маски отобразить прозрачную звезду? Благодаря SVG, мы можем поместить в <mask> несколько элементов.

После добавления ещё одного элемента, SVG-маска <mask> будет выглядеть следующим образом:

<mask id="half">
  <rect x="0" y="0" width="32" height="32" fill="white" />
  <rect x="50%" y="0" width="32" height="32" fill="black" />
</mask>

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

Рассмотрим следующее изображение, на котором визуально объясняется каждый элемент маски.

Обратите внимание, что белый прямоугольник спозиционирован в точке с координатами 0, 0, в то время как чёрный — в точке 50%, 0. Визуально это выглядит следующим образом:

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

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

<mask id="half">
  <rect x="0" y="0" width="32" height="32" fill="white" />
  <rect x="50%" y="0" width="32" height="32" fill="grey" />
</mask>

Давайте подведём итог с полной разметкой

<svg width="0" height="0" viewBox="0 0 32 32">
   <defs>
      <mask id="half">
         <rect x="0" y="0" width="32" height="32" fill="white" />
         <rect x="50%" y="0" width="32" height="32" fill="grey" />
      </mask>

      <symbol viewBox="0 0 32 32" id="star">
         <path d="M31.547 12a.848.848 0 00-.677-.577l-9.427-1.376-4.224-8.532a.847.847 0 00-1.516 0l-4.218 8.534-9.427 1.355a.847.847 0 00-.467 1.467l6.823 6.664-1.612 9.375a.847.847 0 001.23.893l8.428-4.434 8.432 4.432a.847.847 0 001.229-.894l-1.615-9.373 6.822-6.665a.845.845 0 00.214-.869z" />
      </symbol>
   </defs>
</svg>

<p class="c-rate">
   <svg class="c-icon" width="32" height="32" viewBox="0 0 32 32">
      <use xlink:href="#star" mask="url(#half)" fill="green"></use>
   </svg>
   <!-- Остальные 4 звезды -->
</p>

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

Демонстрация

Половина звезды с помощью SVG-градиента

Рассмотрим второй способ реализации частичной заливки. Во время поисков решения мне понравился вот этот ответ на Stackoverflow.

Подобно маске, в этом случае в элементе <defs> нужно определить градиент.

<svg width="0" height="0" viewBox="0 0 32 32">
  <defs>
    <linearGradient id="half" x1="0" x2="100%" y1="0" y2="0">
      <stop offset="50%" stop-color="#f7efc5"></stop>
      <stop offset="50%" stop-color="#fed94b"></stop>
    </linearGradient>
  </defs>
</svg>

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

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

<svg width="0" height="0" viewBox="0 0 32 32">
  <defs>
    <!-- Градиент, который будет задавать два цвета -->
    <linearGradient id="half" x1="0" x2="100%" y1="0" y2="0">
      <stop offset="50%" stop-color="#fed94b"></stop>
      <stop offset="50%" stop-color="#f7efc5"></stop>
    </linearGradient>
    
    <!-- Фигура звезды -->
    <symbol viewBox="0 0 32 32" id="star">
      <path d="M31.547 12a.848.848 0 00-.677-.577l-9.427-1.376-4.224-8.532a.847.847 0 00-1.516 0l-4.218 8.534-9.427 1.355a.847.847 0 00-.467 1.467l6.823 6.664-1.612 9.375a.847.847 0 001.23.893l8.428-4.434 8.432 4.432a.847.847 0 001.229-.894l-1.615-9.373 6.822-6.665a.845.845 0 00.214-.869z" />
    </symbol>
  </defs>
</svg>

<!-- Пример применения звезды с наложением градиента -->
<p class="c-rate">
    <svg class="c-icon" width="32" height="32" viewBox="0 0 32 32">
        <use xlink:href="#star" fill="url(#half)"></use>
    </svg>
</p>

Демонстрация

Стилизация контура

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

Контур при использовании SVG-маски

Чтобы добавить контур, всё что нам нужно, это добавить SVG-элементу stroke. Это будет хорошо работать для полной звезды. Однако, у частичной звезды контур будет обрезанным из-за маски.

Чтобы решить эту проблему, достаточно просто создать ещё одну фигуру звезды именно для контура. Мы можем сделать это, продублировав элемент <use>, но уже без маски.

<svg width="0" height="0" viewBox="0 0 32 32">
  <defs>
    <!-- Маска, которая будет закрывать часть звезды -->
    <mask id="half">
      <rect x="0" y="0" width="32" height="32" fill="white" />
      <rect x="50%" y="0" width="32" height="32" fill="grey" />
    </mask>

    <!-- Фигура звезды -->
    <symbol viewBox="0 0 32 32" id="star">
      <path d="M31.547 12a.848.848 0 00-.677-.577l-9.427-1.376-4.224-8.532a.847.847 0 00-1.516 0l-4.218 8.534-9.427 1.355a.847.847 0 00-.467 1.467l6.823 6.664-1.612 9.375a.847.847 0 001.23.893l8.428-4.434 8.432 4.432a.847.847 0 001.229-.894l-1.615-9.373 6.822-6.665a.845.845 0 00.214-.869z" />
    </symbol>
  </defs>
</svg>

<p class="c-rate">
  <svg class="c-icon" width="32" height="32" viewBox="0 0 32 32">
    <!-- Фигура звезды с заливкой с использованием маски -->
    <use href="#star" mask="url(#half)" fill="green"></use>
    <!-- Фигура звезды без заливки, но с обводкой -->
    <use href="#star" fill="none" stroke="grey"></use>
  </svg>
</p>

Обратите внимание, что у нас стало два элемента <use>. Один — с маской, делающей фон половины звезды полупрозрачным, другой элемент — только с контуром без применения маски.

Демонстрация

Контур при использовании SVG-градиента

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

<svg style="width: 0; height: 0;" viewBox="0 0 32 32">
   <defs>
      <linearGradient id="half" x1="0" x2="100%" y1="0" y2="0">
        <stop offset="50%" stop-color="#f7efc5"></stop>
        <stop offset="50%" stop-color="#fed94b"></stop>
      </linearGradient>
   </defs>
</svg>

<p class="c-rate">
   <svg class="c-icon" width="32" height="32" viewBox="0 0 32 32">
     <use xlink:href="#star" fill="url(#half)" stroke="grey"></use>
   </svg>
</p>

Демонстрация

Размер

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

.c-icon {
    width: var(--size, 24px);
    height: var(--size, 24px);
}

.c-icon--md {
    --size: 40px;
}

.c-icon--lg {
    --size: 64px;
}

Надеюсь, вам понравилась данная статья. Благодарю за прочтение.

Adblock test (Why?)

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


Когда много работаешь мозгом и не слишком часто поднимаешь пятую точку с «компьютерного» кресла — лишний вес и прочие проблемы неправильного питания часто становятся проблемой. Знаю по себе не понаслышке: четыре года напряжённой и не всегда нормированной аналитической работы в своё время превратили меня из 75-килограммового парня в 105-килограммового увальня. Потом пришлось заморачиваться, чтобы за полгода сбросить 20-25 лишних кило.

А ведь когда часами работаешь над сложной или хотя бы объёмной задачей с полным погружением — организм требует «жрааать» не хуже кота в пять утра. И поесть полноценно не всегда получается. Значит, кидаем в рот то, что позволяет заглушить чувство голода без отрыва от монитора. И часто кидаем мы туда разные отнюдь не душе- и тушкоспасительные вещи. Шоколадные батончики, быстрорастворимая лапша, чипсы, пиццу, бургеры… ну вы знаете. Вкусно, голод глушит, стресс тоже, настроение поднимает — только вот (trigger warning!) на пользу это не идёт. И чем дальше, тем больше.


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

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

▍ Инжир. Свежий или сушёный



Весьма полезный фрукт, в том числе для нашего многострадального мозга. Не случайно есть мнение, что именно инжир был тем самым запретным плодом познания добра и зла, коим пресловутый змий соблазнил Адама с Евой. По сути своей, плод инжира — это мультивитаминный комплекс с калием, железом, магний, железом, медью, а также витаминами В1, В3, В6, PP и C. Ещё в нём много клетчатки — которая отлично помогает убрать чувство голода и полезна для пищеварения. Полезен инжир для сердечно-сосудистой системы, мешая образованию тромбов и уменьшая вероятность инсультов с инфарктами. Попутно инжир ещё и препятствует жирообразованию.

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


Особенно хорошо и быстро «подзаряжает» мозг сушёный инжир. Да, он выглядит не очень эстетично, слегка хтонично и немного напоминает о фильмах Гильермо дель Торо — но из сухофруктов его можно отнести к самым полезным. Его можно хранить очень долго — в отличие от свежего инжира, который часто «умирает» за несколько дней даже в холодильнике. Однако стоит помнить: сушёный инжир за счёт отсутствия воды на единицу массы в три раза калорийнее свежего, и «быстрые» углеводы — всё же не то, чем стоит увлекаться. И витаминов в сушёном заметно меньше. «Налегать» на сушёный инжир стоит ещё меньше, чем на свежий — лучше не превышать пары штук в сутки. Но всё же в умеренном количестве и сушёный инжир весьма полезен.

А ещё инжир помогает от похмелья.

▍ Тёмный шоколад с высоким содержанием какао



Шоколад сам по себе звучит не очень зожно. Но некоторые из нас буквально жить не могут без шоколада, и испытывают без него натуральную «ломку». А лишний стресс нам тоже ни к чему. В то же время, в тёмном шоколаде с содержанием какао-бобов не менее 80% максимум пользы и минимум вреда. И чем больше процент бобов — тем лучше (хотя и менее вкусно, как считают многие).

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


Ещё, если не лень заморочиться, тёмный шоколад можно растапливать, окунать в него свежие фрукты и ставить в холодильник (не сразу! Сначала пусть остынут до комнатной температуры). Получается ещё вкуснее, полезнее и зожнее. Хорошо сочетается тёмный шоколад с высоким содержанием какао и с орехами — которые также полезны для работы мозга (хоть и весьма калорийны).

Только увлекаться даже самым «правильным» шоколадом тоже не стоит. 20-30 грамм, не больше.

▍ Фитнес-батончики



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

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


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

▍ Фрукты и овощи в целом



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

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

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

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

▍ Цельнозерновые хлебцы



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

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

▍ Молокопродукты: йогурты и творог



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

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

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

▍ Зерновые каши



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

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


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

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

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


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

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

Adblock test (Why?)

Некомими: обзор IBM ThinkPad S30


Если бы кто-то решил провести опрос на тему «С чем у вас ассоциируется ThinkPad‎», ответы «няшность‎» и «милота‎» точно были бы в самом низу рейтинга. Суровые черные кирпичики, дизайн которых был рожден сумрачным японским гением в недрах лаборатории IBM в Ямато после сытного обеда и навеян традиционным японским кейсом для кушаний, бенто. И бенто этот точно был эргономичным и очень прочным (и с классным трекпойнтом), под стать суровым японским самураям.

В качестве ассоциации, скорее, уместно в третий раз использовать слово суровый. Или хотя бы крепкий, надежный, удобный. Но японцы не были бы японцами, не сумей они сделать для себя любимых что-то особенное. Например, няшный ThinkPad с милыми ушками.
Итак, сегодня мы заглянем ровно на 20 лет назад, в 2001 год. Вот-вот выйдет Windows XP, но балом правят Windows 98 и Millennium Edition, а люди посерьезнее используют Windows 2000. Linux на десктопе — все еще мечта энтузиастов, а рабочие станции Unix еще живы, хотя и постепенно уходят с рынка.

Настольные компьютеры в основном продаются с Pentium III или Celeron, но на «‎вершине пищевой цепочки»‎ уже обосновался неоднозначный Pentium 4 c RDRAM и Socket 423. Athlon стремительно догоняет его по частотам, а по производительности как минимум не уступает.

В ноутбуках Pentium III практически безальтернативен, хотя в бюджетном сегменте встречаются Celeron и даже AMD K6. Вершина модельного ряда ThinkPad — модели Т22 и А22р с мощными гигагерцовыми процессорами и дискретными 3D-ускорителями. Субноуты представлены моделью Х21 с диагональю 12” и массой около 1.6 кг.

У других компаний есть еще более легкие и компактные машинки, как правило, с очень слабыми процессорами — Pentium MMX (последний обновился аж в 1999 году!), Transmeta Crusoe и даже Cyrix MediaGX. Особенно любили такие ультрапортативные компьютеры в Японии и других странах Азии. IBM же недавно сняла с производства свой ThinkPad 240Z, и замена не заставила себя долго ждать.

Новинка была отнесена к i Series, но при этом получила индекс в стиле основной линейки — S30. Кстати, S30 и ее обновление — S31, отличавшееся только увеличенной в полтора раза емкостью диска, стали последними представителями i Series. Что означала литера S — тайна, покрытая мраком, а вот число 30 отделяла эти ноутбуки от актуальной двадцатой линейки и приближало к грядущим A30 и R30, которые должны были выйти осенью того же года.

Изначально новая модель планировалась исключительно для локального рынка Японии, но в итоге продавалась и в других странах Азии, например Китае и Южной Корее. Японский рынок примечателен тем, что множество интересных товаров были выпущены исключительно для него. Публика здесь крайне взыскательна, но при этом открыта к экспериментам и любит эксклюзив. S30 можно отнести ко всем трем категориям.

Голова — два уха



Первое, что бросается в глаза, — действительно небольшой размер. Всего 258х198 мм с учетом всех выступающих частей. Толщина — 32 мм определяется в первую очередь батареей, далее корпус плавно «худеет‎» до 22 мм. Для тех времен — обычный результат.

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

На боковых гранях обращают на себя внимание небольшие выступы — те самые ушки, но пока ноутбук закрыт их назначение неочевидно. Под левым приютились слоты PCMCIA и Compact Flash, рядом — USB порт и разъем питания, проприетарный VGA-видеовыход и миниатюрная версия FireWire. Контроллер FireWire был опцией, и на части машин порт был с завода закрыт заглушкой.

Справа — звуковые миниджеки, еще один USB, модемный и сетевой порты. На этом возможности расширения заканчиваются — нет ни порта док-станции, ни UltraPort или ИК-порта. Жаль, док был бы очень полезен для такого малыша.
Еще о легендарных ноутбуках линейки ThinkPad:
Чтобы открыть крышку, придется использовать обе руки — как на более ранних моделях. Стоит ее открыть, как вся история с «ушами‎» становится понятной. Выступы возле экрана, окруженного в меру широкими рамками (около 1 см), ассоциируются именно с ними. Правда, не со стереотипными кошачьими — теми самыми «некомими‎», а с обычными человеческими, расположенными по бокам от головы.

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

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

Что прячется в бенто



Начинка не далеко ушла от предшественника. Все тот же Pentium III с частотой 600 МГц и 256 Кбайт кэша. Но, в отличие от прежних моделей, новые ноутбуки получили сверхнизковольтную (ULV) версию процессора. Это позволило упростить систему охлаждения, хотя полностью сделать ее пассивной пока не получилось. Компанию процессору составил чипсет Intel 440MX, одночиповое решение, единственное подобное в серии 440.

За счет этого плата получилась очень компактной. Без жертв не обошлось: здесь только один слот памяти, что ограничивает ее максимальный объем до 384 Мбайт, 128 из которых интегрировано на плату. Более чем достаточно для полноценной работы в Windows ME и Windows 2000, с которыми поставлялся этот компьютер. Причем в заводской поставке всех версий устанавливался модуль на 128 Мбайт, чего, впрочем, тоже достаточно.

Крошечный ноутбук получил дискретную видеосистему — ни в процессоре, ни в чипсете встроенного контроллера не было предусмотрено. Эту роль на себя взял Silicon Motion Lynx3DM с 4 Мбайт видеопамяти, интегрированной в одну упаковку с чипом. Да, это та самая SMI, которая теперь выпускает контроллеры для SSD! Карта очень плоха в 3D, но для вывода двухмерной картинки на дисплей с разрешением 1024х768 ее хватало. Сам экран — довольно приятный, но самое главное в нем как раз разрешение — прежде в миниатюрных ноутбуках ограничивались матрицами с низким разрешением — 800х600, а то и 640х480.

Также на материнскую плату интегрирован звуковой адаптер. А вот все остальные устройства выполнены в виде отдельных карт. Модем — в виде небольшой платки проприетарного формата, а сеть и FireWire объединены на комбо-плате MiniPCI. В некоторых версиях вместо комбо-платы устанавливался модуль беспроводной связи Wi-Fi стандарта 802.11b (до 11 Мбит/с).

Опций было немного. Кроме выбора проводной или беспроводной сети, можно было выбрать объем диска — 10 или 20 Гбайт. В следующей модели S31 диски немного подросли — до 15 и 30 Гбайт. Еще одна опция касалась внешнего вида. Впервые для ThinkPad была доступна глянцевая отделка крышки — Piano Black. И это не просто черный глянцевый пластик, это действительно лаковое покрытие. Смотрится очень стильно и дорого.

В коллекции Digital Vintage есть обе версии. Матовая с проводной сетью, FireWire, диском на 10 Гбайт и Piano Black с беспроводной сетью и 20-гигабайтным хардом. У обоих установлено 256 Мбайт памяти, работают они под управлением Windows 2000 Professional. По сей день этот компьютер прекрасно подходит для работы с текстом или игры в старые нетребовательные игры, например вездесущие «Герои Меча и Магии III».


Непросто оказалось подключиться к современной точке доступа — Windows 2000 не поддерживает WPA и WPA2, в целом, поддержка Wi-Fi была в зачаточном состоянии. Спасла программа Wi-Fi Hopper, которая умеет перехватывать управление адаптером и своими силами обеспечивает поддержку шифрования.

Заключение


На поиски S30 для коллекции у меня ушло два года. Дело было не только в небольшом объеме выпуска. За пределами Японии найти такой компьютер весьма непросто. Приобрести пришлось два экземпляра — первый застрял на почте, и после полугода ожидания я заказал второй. Благо, посылка была застрахована, и возмещение я получил.

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

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

Спасибо за понимание и до новых встреч!

P.S. Я не забыл про заключительную часть рассказа об истории Альфы. Просто готовлю небольшой сюрприз для верных читателей.

Adblock test (Why?)

Microsoft запретила перехват ссылок microsoft-edge:// в превью-сборках Windows 11

Пользователей в Сети заинтересовали изменения между сборками Windows 11 22483 и 22494 (обе сборки Windows Insider Preview). Выяснилось, что больше нельзя обойти Microsoft Edge с помощью таких приложений, как EdgeDeflector. Это было обнаружено по нескольким упоминаниям в журнале изменений сборки в протоколе системе ассоциации файлов и приложений по умолчанию. 

EdgeDeflector — приложение, перехватывающие ссылки, начинающиеся с  «microsoft-edge: //», и заменяющее их на «https: //», что позволяет открывать их установленным по умолчанию браузером. Начиная с Windows 10 Microsoft добавила всевозможные виджеты для погоды, новостей, магазинов, встроенного сервиса игр Xbox и т.д. И вместо обычных ссылок «https: //» они используют формат «microsoft-edge: //», который работает только с веб-браузером компании Microsoft. 

По мнению разработчика EdgeDeflector, эти ссылки существуют с целью агрессивного продвижения Microsoft Edge в пользовательской среде. Другие пользователи в Сети разделяют это мнение. Оно косвенно подтверждается и  различиями в установке по умолчанию браузера в Windows 10 и 11. Если раньше в 10 можно было просто выставить один раз по умолчанию для всех протоколов браузер, то в 11 для каждого веб-протокола надо выставлять отдельно браузер по умолчанию. А еще при скачивании EdgeDeflector браузер Microsoft Edge ругается на это ПО и  называет его небезопасным.

Кроме EdgeDeflector похожий функционал недавно появился в двух других браузерах. Так, в Brave добавлена поддержка microsoft-edge:схемы URL с версией 1.30.86. А компания Mozilla поставила перед разработчиками задачу обойти стандартную защиту браузера в Windows 11, и это им удалось. Разработчик Масатоши Кимура написал патчи для реализации протокола в Firefox, но они еще не прошли проверку и не были объединены с Firefox. 

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

Какие же отличия в build 22494 Windows 11? В нем больше нельзя устанавливать по умолчанию что-либо, кроме Microsoft Edge,  для ссылок с префиксом «microsoft-edge: //». Точнее, выбор есть между версиями Microsoft Edge, Microsoft Edge (Beta-версия) и Microsoft Edge (версия для разработчиков). Другие браузеры использовать не получится.  Также невозможно сменить ассоциацию протокола по умолчанию с нужным приложением при помощи изменений реестра или пакета Microsoft Edge, вмешательства в OpenWith.exe и прочих хакерских ухищрений.

Adblock test (Why?)

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

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

Центрифуга SpinLaunch
Центрифуга SpinLaunch

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

Первый испытательный полет прототипа — так называемого суборбитального ускорителя — состоялся 22 октября в космодроме Америка в Нью-Мексико, но об этом событии компания объявила только в ноябре.

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

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

«Это радикально другой способ ускорения снарядов и ракет-носителей до гиперзвуковых скоростей с использованием наземной системы», — сказал генеральный директор SpinLaunch Джонатан Яни.

Яни основал SpinLaunch в 2014 году, но до сих пор компания оставалась в тени. Суборбитальный ускоритель, использованный в первом тесте SpinLaunch, представляет собой версию запланированного финального оборудования в масштабе одного к трем. Его высота составляет 91 метр. 

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

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

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

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

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

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

SpinLaunch заявляет, что завершила около 90% исследований по снижению рисков, связанных с развертыванием полномасштабной системы и работает над завершением ее дизайна.

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

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

Аналогичную работу ведет стартап Green Launch, который использует другой подход с наземными «импульсными пусковыми установками» для замены первой ступени традиционной ракеты. Летом Green Launch провела испытания на полигоне армии США Юма в Аризоне.

Предприятие SpinLaunch на данный момент привлекло $110 млн от различных инвесторов и нацелено непосредственно на коммерческий рынок, но ее полностью зрелая технология вполне может иметь и военные приложения. Пентагон уже проявляет интерес к компании, и в 2019 году был подписан контракт с отделом оборонных инноваций. Сверхдальние артиллерийские и ударные возможности, особенно те, которые могут поразить цели в короткие сроки издалека, являются одними из главных приоритетов вооруженных сил США. 

В 2020 году компания Aevum из Алабамы представила собственную систему запуска спутников под названием Ravn X. Это автономный самолет и ракета-носитель. Система предназначена для доставки спутников в космос каждые 180 минут. Беспилотник имеет полную взлетную массу 25 т. Самолет запускает ракету на высоте от 10 до 20 км. И самолет, и ракета-носитель используют в качестве пропеллента топливо для реактивных двигателей Jet-A, которое доступно почти во всех аэропортах США.

Adblock test (Why?)

Патент Apple описывает очки приватности для смартфона

Бюро по патентам и товарным знакам США на этой неделе опубликовало новый патент, поданный Apple, который раскрывает функцию конфиденциальности, предназначенную для отображения содержимого iPhone только через специальные очки.

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

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

iPhone выполняет распознавание лица (Face ID) пользователя и использует систему # 102 оптических датчиков. Фронтальная камера №105 (например, камера, предназначенная для обнаружения видимого, инфракрасного и / или ультрафиолетового света, не показана) может захватывать изображения точечного рисунка (например, части точечного рисунка, которые отражаются от лица пользователя) и создавать карту глубины биометрической идентичности и / или набор карт биометрической идентичности лица пользователя на основе расстояния между отдельными точками.
iPhone выполняет распознавание лица (Face ID) пользователя и использует систему # 102 оптических датчиков. Фронтальная камера №105 (например, камера, предназначенная для обнаружения видимого, инфракрасного и / или ультрафиолетового света, не показана) может захватывать изображения точечного рисунка (например, части точечного рисунка, которые отражаются от лица пользователя) и создавать карту глубины биометрической идентичности и / или набор карт биометрической идентичности лица пользователя на основе расстояния между отдельными точками.

В патенте описан сценарий, когда пользователь может взаимодействовать с графиком калибровки, чтобы намеренно размыть графический вывод, представленный на дисплее iPhone. На рисунке 2А выше показан вид спереди электронного устройства, отображающего стандартный графический вывод; 2B иллюстрирует вид спереди электронного устройства, отображающего графический вывод с коррекцией зрения; 3А демонстрирует вид спереди электронного устройства, отображающего меню настройки технического зрения. На рисунке 3A виден примерный экран для меню настроек зрения. Электронное устройство включает в себя систему #302 оптических датчиков, которая может иметь модуль # 303 излучения света и камеру # 305. Также предусмотрен специальный ползунок, где пользователь может настроить размытие картинки. Меню включает опции «Близорукость», «Дальнозоркость», «Пресбиопия» и «Астигматизм».

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

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

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

В сентябре Facebook презентовала умные очки Ray-Ban Stories, которые позволят снимать фото и видео, делиться впечатлениями, слушать музыку и принимать телефонные звонки. Устройства разработали в партнерстве с материнской компанией Ray-Ban EssilorLuxottica. Они стоят от $299.

Управлять камерой устройства можно с помощью кнопки записи на самих очках или с использованием голосовых команд Facebook Assistant. Ray-Ban Stories будут работать с новым приложением Facebook View, что позволит пользователям делиться медиаконтентом с друзьями и подписчиками в соцсетях. Приложение будет включать опции импорта, редактирования и публикации контента в Facebook, Instagram, WhatsApp, Messenger, Twitter, TikTok, Snapchat и другие соцсети. Кроме того, контент можно будет сохранять в фотогалерее телефона, чтобы редактировать и делиться уже со смартфона.

Facebook пока не называет свои очки AR-разработкой, а позиционирует их именно как умное устройство.

Adblock test (Why?)

Новый метод позволил впервые найти чёрную дыру в молодом звёздном скоплении за пределами нашей Галактики

Астрономы, воспользовавшись «Очень большим телескопом» из Европейской южной обсерватории, нашли небольшую чёрную дыру за пределами Млечного Пути. Её положение вычислили, проанализировав движение звёзд поблизости. Такой метод впервые был использован для обнаружения чёрной дыры за пределами нашей Галактики. Результаты исследования опубликованы в журнале Monthly Notices of the Royal Astronomical Society.

Чёрную дыру заметили в NGC 1850 – рассеянном скоплении, находящемся в Большом Магеллановом Облаке, соседней карликовой галактике. Расстояние до него составляет около 163 тысяч световых лет.

Исследованием руководила Сара Сарацино из Астрофизического исследовательского института при Ливерпульском университете Джона Мурса. По её словам, для обнаружения чёрных дыр астрономы изучают траектории каждой звезды в скоплении, и пытаются таким образом вычислить находящиеся там чёрные дыры, не имея возможности наблюдать их напрямую. Отработанная схема позволит находить другие чёрные дыры в иных скоплениях звёзд.
Данная чёрная дыра оказалась примерно в 11 раз массивнее Солнца. Выдало её гравитационное влияние, которое она оказывала на вращающуюся вокруг неё звезду массой в пять солнечных.

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

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

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

Adblock test (Why?)

[Перевод] Reversing для чайников — ассемблер x86 и код на С (для начинающих/ADHD friendly)

До того как заняться реверс-инжинирингом, исполняемые файлы казались мне черной магией. Я всегда интересовался, как все работает под капотом, как двоичный код представлен внутри .exe файлов, и насколько сложно модифицировать “исполняемый код” без доступа к исходникам.

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

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

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

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

Вступление

Компиляция

Код, написанный на компилируемом языке, компилируется (еще бы) в выходной двоичный файл (например, exe. файл).

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

Двоичный код

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

Преимущественно, это арифметические инструкции. Они манипулируют регистрами/флагами CPU, а также энергозависимой памятью по мере выполнения.

Регистры процессора

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

Важно отметить наличие специального регистра, называемого FLAGS (EFLAGS в 32-битном формате), в котором находится набор флагов (логических индикаторов), содержащих информацию о состоянии процессора, включая сведения о последней арифметической операции (ноль: ZF; переполнение: OF; четность: PF; знак: SF и т. д.).

Регистры CPU, визуализированные при отладке 32-разрядного процесса в инструменте отладки x64dbg.
Регистры CPU, визуализированные при отладке 32-разрядного процесса в инструменте отладки x64dbg.

Некоторые из этих регистров представлены во фрагменте, приведенном выше, а именно: EAX, ESP (указатель стека), EBP (базовый указатель).

Доступ к памяти

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

Стек

Более простая и быстрая из двух сущностей — это линейная непрерывная структура данных LIFO (последний вошел = первый вышел) с механизмом push/pop. Служит для хранения локальных переменных, аргументов функций и отслеживания вызовов (слышали когда-либо о трассировке стека?)

Куча

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

Инструкции ассемблера

Как я упоминал ранее, ассемблерные инструкции имеют разный «размер в байтах» и различное количество операндов.

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

55         push    ebp     ; size: 1 byte,  argument: register
6A 01      push    1       ; size: 2 bytes, argument: immediate

Давайте быстро пробежимся по очень небольшому набору некоторых из наиболее употребляемых команд — не стесняйтесь самостоятельно изучать для получения более подробной информации:

Стековые операции

  • push value; помещает значение в стек (ESP уменьшается на 4, размер одной «единицы» стека).

  • pop register; помещает значение в регистр (ESP увеличивается на 4).

Передача данных

  • mov destination, source; копирует значение из/в регистр.

  • mov destination, [expression]; копирует значение из памяти по адресу, получаемому из ‘регистрового выражения’ (одиночный регистр или арифметическое выражение, содержащее один или больше регистров) в регистр.

Поток выполнения

  • jmp destination; переходит к команде по адресу (устанавливает EIP (указатель инструкций)).

  • jz/je destination; переходит к команде по адресу, если установлен ZF (нулевой флаг).

  • jnz/jne destination; переходит к команде по адресу, если ZF не установлен.

Операции

  • сmp operand1, operand2; сравнивает 2 операнда и устанавливает ZF, если они равны.

  • add operand1, operand2; операнд1 += операнд2.

  • sub operand1, operand2; операнд1 -= операнд2.

Переходы функций

  • call function; вызывает функцию (помещает текущее значение EIP в стек, затем переходит в функцию).

  • retn; возврат в вызываемую функцию (извлекает из стека предыдущее значение EIP).

Примечание: вы могли заметить, что слова «равно» и «ноль» взаимозаменяемы в терминологии x86 — это из-за того, что инструкции сравнения внутренне выполняют вычитание, которое означает, что, если два операнда равны, то устанавливает ZF.

Шаблоны в ассемблере

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

Пролог функции

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

Обычно он выглядит так (X — число):

55          push    ebp        ; preserve caller function's base pointer in stack
8B EC       mov     ebp, esp   ; caller function's stack pointer becomes base pointer (new stack frame)
83 EC XX    sub     esp, X     ; adjust the stack pointer by X bytes to reserve space for local variables

Эпилог функции

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

8B E5    mov    esp, ebp    ; restore caller function's stack pointer (current base pointer) 
5D       pop    ebp         ; restore base pointer from the stack
C3       retn               ; return to caller function

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

Соглашения о вызовах: __cdecl

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

Мы рассмотрим соглашение __cdecl (от C declaration), которое является стандартным при компиляции кода С.

В __cdecl (32-bit) аргументы функции передаются в стек (помещаются в обратном порядке), а возвращаемое значение передается через EAX регистр (при условии, что это не число с плавающей точкой).

Это означает, что при вызове func(1, 2, 3) будет сгенерировано следующее:

6A 03             push    3
6A 02             push    2
6A 01             push    1
E8 XX XX XX XX    call    func

Собираем все вместе

Предположим, что func() просто складывает аргументы и возвращает результат. Вероятно, это будет выглядеть так:

int __cdecl func(int, int, int):

           prologue:
55           push    ebp               ; save base pointer
8B EC        mov     ebp, esp          ; new stack frame

           body:
8B 45 08     mov     eax, [ebp+8]      ; load first argument to EAX (return value)
03 45 0C     add     eax, [ebp+0Ch]    ; add 2nd argument
03 45 10     add     eax, [ebp+10h]    ; add 3rd argument

           epilogue:
5D           pop     ebp               ; restore base pointer
C3           retn                      ; return to caller

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

  1. Почему мы должны сместить EBP на 8, чтобы получить первый аргумент?

    Если вы проверите определение инструкции call, упоминаемой ранее, то поймете, что внутренне она помещает значение EIP в стек. И если вы проверите определение команды push, то обнаружите, что она уменьшает значение ESP (которое скопировано в EBP в прологе) на 4 байта. К тому же, первая инструкция пролога — это также push, поэтому получаем 2 декремента по 4, следовательно, необходимо добавить 8.

  2. Что случилось с прологом и эпилогом, почему они кажутся «усеченными»?

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

Условный оператор

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

Предположим, у нас есть следующая функция:

void print_equal(int a, int b) {
    if (a == b) {
        printf("equal");
    }
    else {
        printf("nah");
    }
}

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

void __cdecl print_equal(int, int):

     10000000   55                push   ebp
     10000001   8B EC             mov    ebp, esp
     10000003   8B 45 08          mov    eax, [ebp+8]       ; load 1st argument
     10000006   3B 45 0C          cmp    eax, [ebp+0Ch]     ; compare it with 2nd
  ┌┅ 10000009   75 0F             jnz    short loc_1000001A ; jump if not equal
  ┊  1000000B   68 94 67 00 10    push   offset aEqual  ; "equal"
  ┊  10000010   E8 DB F8 FF FF    call   _printf
  ┊  10000015   83 C4 04          add    esp, 4
┌─┊─ 10000018   EB 0D             jmp    short loc_10000027
│ ┊
│ └ loc_1000001A:
│    1000001A   68 9C 67 00 10    push   offset aNah    ; "nah"
│    1000001F   E8 CC F8 FF FF    call   _printf
│    10000024   83 C4 04          add    esp, 4
│
└── loc_10000027:
     10000027   5D                pop    ebp
     10000028   C3                retn

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

В случае, если вам интересно, зачем нужна команда add esp, 4, то это просто приведение ESP к исходному значению (такой же эффект, что и у pop, только без изменения какого-либо регистра), поскольку у нас есть push строкового аргумента для printf.

Базовые структуры данных

Давайте двигаться дальше. Поговорим о том, как хранятся данные (особенно целые числа и строки).

Endianness

Endianness — это порядок байтов, представляющих значение в памяти компьютера.

Есть 2 типа: big-endian и little-endian

Для справки, процессоры семейства x86 (которые есть практически на любом компьютере) всегда используют little-endian.

Чтобы привести живой пример этой концепции, я скомпилировал консольное приложение на С++ в Visual Studio, в котором объявил переменную int со значением 1337, а затем вывел адрес переменной, используя функцию printf().

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

Уточним этот момент — переменная int имеет длину 4 байта (32 бита) (на случай, если вы не знали), поэтому это означает, что если переменная начинается с адреса D2FCB8, то она заканчивается прямо перед D2FCBC (+4).

Чтобы перейти от значения, удобочитаемого человеком, к байтам памяти, выполните следующие действия:

Десятичное: 1337 -> шестнадцатеричное: 539 -> 00 00 05 39 -> little-endian: 39 05 00 00

Знаковые целые числа

Это часть интересна, но относительно проста. Здесь вы должны знать, что представление знака у целых чисел (положительных/отрицательных) обычно выполняется на компьютерах с помощью концепции, называемой дополнением до двух.

Суть в том, что наименьшая/первая половина целых чисел зарезервирована для положительных чисел, а наибольшая/вторая половина предназначена для отрицательных, вот как это выглядит в шестнадцатеричном формате для 32-битного знакового int (выделено = шестнадцатеричный формат, в скобках = десятичный):

Положительные (1/2): 00000000 (0) -> 7FFFFFFF (2 147 483 647 или INT_MAX)

Отрицательные (2/2): 80000000 (-2 137 483 648 или INT_MIN) -> FFFFFFFF (-1)

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

Совет: калькулятор Windows — очень полезный инструмент. Вы можете войти в режим программиста и установить размер в DWORD (4 байта), затем ввести отрицательные десятичные значения и визуализировать их в шестнадцатеричном и двоичном формате, получая удовольствие от выполнения операций с ними.

Строки

В C строки хранятся в виде массивов char, поэтому здесь нет ничего особенного, кроме того, что называется null termination.

Если вы когда-нибудь задумывались, как strlen() узнает размер строки, то все очень просто — строки имеют символ, обозначающий их конец, и это нулевой байт/символ — 00 или ‘\0’.

Если вы объявите строковую константу в C и наведете на нее курсор, например, в Visual Studio, то покажется размер сгенерированного массива, и, как можете видеть, в нем на один элемент больше, чем в видимом размере строки.

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

Смысл call и jmp инструкций

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

Возьмем пример с print_equal(), но на этот раз сосредоточимся только на инструкциях call printf().

void print_equal(int, int):
...
     10000010   E8 DB F8 FF FF    call   _printf
...
     1000001F   E8 CC F8 FF FF    call   _printf

Вы можете спросить себя — подождите секунду, если это одни и те же инструкции, то почему их байты разные?

Это потому, что инструкции calljmp) (обычно) принимают в качестве операнда смещение (относительный адрес), а не абсолютный адрес.

Смещение — это в основном разница между текущим местоположением в памяти и пунктом назначения. Это также означает, что оно может быть как отрицательным, так и положительным.

Как вы можете видеть, опкод инструкции call, содержащей 32-битное смещение, — это E8. После него следует само смещение — полная инструкция имеет вид: E8 XX XX XX XX.

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

Вы заметите, что это разница такая же, как разница между адресами инструкций (10000001F10000010 = F):

Еще одна небольшая деталь, о которой нужно упомянуть — процессор выполняет инструкцию только после ее полного «чтения». Это означает, что к тому времени, когда CPU начинает «выполнение», EIP (указатель инструкций) уже указывает на следующую команду.

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

Теперь применим все это, чтобы узнать адрес printf() из первой инструкции в примере:

10000010   E8 DB F8 FF FF    call   _printf
  1. Извлеките смещение из инструкции E8 (DB F8 FF FF) -> FFFFF8D8 (-1829)

  2. Сложите его с адресом инструкции: 100000010 + FFFFF8D8 = 0FFFF8EB

  3. И наконец, прибавьте размер инструкции: 0FFFF8EB + 5 = OFFFF8F0 (&printf)

Точно такой же принцип применяется к инструкции jmp:

...
┌─── 10000018   EB 0D             jmp    short loc_10000027
...
└── loc_10000027:
     10000027   5D                pop    ebp
...

Единственное отличие в этом примере состоит в том, что EB XX — это короткая версия jmp. Это означает, что в ней используется только 8-битное (1 байт) смещение.

Следовательно: 10000018 + 0D + 2 = 10000027

Вывод

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

Начните с написания игрушечного кода на С, его компиляции и пошаговой отладки (Visual Studio, кстати, позволяет это сделать).

Compiler Explorer также является чрезвычайно полезным веб-сайтом, который компилирует код C в ассемблер в реальном времени с использованием нескольких компиляторов (выберите компилятор x86 msvc для 32-разрядной версии Windows).

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


Дата-центр ITSOFT — размещение и аренда серверов и стоек в двух дата-центрах в Москве. UPTIME 100%. Размещение GPU-ферм и ASIC-майнеров, аренда GPU-серверов, лицензии связи, SSL-сертификаты, администрирование серверов и поддержка сайтов.

Adblock test (Why?)