Сборка Intel X86: как сказать, многоразрядный аргумент? - PullRequest
0 голосов
/ 08 мая 2018

В следующей сборке:

mov     dx, word ptr [ebp+arg_0]
mov     [ebp+var_8], dx

Думая об этом как о собранной функции C, сколько битов в ширину (аргумент функции C) arg_0? Сколько бит в ширину (локальная переменная C) var_8? То есть, короткая ли, целая и т. Д.

Отсюда видно, что var_8 - 16 бит, поскольку dx - 16-битный регистр. Но я не уверен насчет arg_0.

Если сборка также содержит эту строку:

ecx, [ebp+arg_0]

Значит ли это, что arg_0 является 32-битным значением?

1 Ответ

0 голосов
/ 08 мая 2018

Для решения этого вопроса необходимо понять три принципа.

  1. Ассемблер должен иметь возможность определить правильную длину.
    Хотя в синтаксисе Intel не используется суффикс размера, такой как AT & T синтаксис , ассемблеру по-прежнему нужен способ определить размер операндов.

    Неоднозначная инструкция mov [var], 1 записывается как movl $1, var в синтаксисе AT & T, если размер хранилища 32-разрядный (обратите внимание на суффикс l), поэтому легко определить размер непосредственного операнда.
    Ассемблеру, который принимает синтаксис Intel, нужен способ вывести этот размер, есть четыре широко используемых параметра:

    • Выводится из другого операнда.
      Это тот случай, когда используется регистр, например.
      Например. mov [var], dx - 16-битный магазин.
    • Это указано явно.
      mov WORD [var], dx
      Ассемблерам MASM-синтаксиса требуется PTR после размера, поскольку их спецификаторы размера разрешены только для операндов памяти, а не для непосредственных объектов или где-либо еще.
      Это форма, которую я предпочитаю, потому что она понятна, она выделяется, и она менее подвержена ошибкам (mov WORD [var], edx недопустимо).
    • Выводится из контекста.

       var db 0
      
       mov [var], 1   ; MASM/TASM only.   associate sizes with labels 
      

      Ассемблеры MASM-синтаксиса могут сделать вывод, что, поскольку var объявлен с db, его размер 8-битный, как и хранилище (по умолчанию).
      Эта форма мне не нравится, потому что она затрудняет чтение кода (одна хорошая вещь в сборке - это «локальность» семантики инструкций) и смешивает высокоуровневые понятия, такие как типы, с низкоуровневыми понятиями, такими как store. размеры. Вот почему синтаксис NASM не поддерживает магическое / нелокальное сопоставление размеров .

    • В большинстве случаев есть только один правильный размер
      Это относится к push, ветвям и всем инструкциям, где размер их операнда зависит от модели памяти или размера кода.
      Используемый фактический размер может быть переопределен для некоторых инструкций, но по умолчанию это разумный выбор. (например, push word 123 против push 123)


    Короче говоря, должен быть способом, которым ассемблер может указать размер, иначе он отклонит код. (Или некоторые некачественные ассемблеры, такие как emu8086, имеют размер операнда по умолчанию для неоднозначных случаев.)

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

  2. Язык C преднамеренно не соответствует тому, как типы C отображаются в число битов

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

    Datatype    LP64    ILP64   LLP64   ILP32   LP32
    char        8       8       8       8       8
    short       16      16      16      16      16
    _int32      32          
    int         32      64      32      32      16
    long        64      64      32      32      32
    long long                   64      [64]                    
    pointer     64      64      64      32      32
    

    Windows на x86_64 использует LLP64. Другие ОС на x86-64 обычно используют x86-64 System V ABI, модель LP64.

  3. Сборка не имеет типов, и программисты могут использовать это

    Даже компиляторы могут использовать это .

    В случае, если связанная переменная bar типа long long (64-разрядная) имеет значение OR, равное 1, clang резервирует префикс REX путем ORing только младшего байта. Это приводит к задержке переадресации магазина, если переменная сразу же перезагружается сразу с двумя загрузками dword или одним qword, поэтому это, вероятно, не лучший выбор, особенно в 32-битном режиме, где or dword [bar], 1 имеет тот же размер и, вероятно, будет перезагружен как две 32-битные половины.
    Если бы кто-то неосторожно взглянул на разобранный код, он мог бы заключить, что bar является 8-битным.
    Подобные уловки, когда переменная или объект доступны частично, являются общими.

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

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

Чтобы окончательно ответить на ваш вопрос

Да, из первого фрагмента кода можно предположить, что значение arg_0 имеет ширину 16 бит.
Обратите внимание, что поскольку в стек передается функция arg, она на самом деле 32-разрядная, но старшие 16 бит не используются.

Если mov ecx, [ebp+arg_0] появилось позже в коде, чем вам бы пришлось пересмотреть свое предположение о размере значения arg_0, оно, безусловно, по крайней мере 32-разрядное.
Маловероятно, что он 64-битный (64-битный тип редко встречается в 32-битном коде, мы можем сделать эту ставку), поэтому мы можем заключить, что он 32-битный.
Очевидно, что первый фрагмент был одним из тех трюков, которые используют только часть переменной.

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

...