2016-04-24 3 views
1

Мне нужна помощь, чтобы понять, что происходит в тот момент, когда этот фрагмент кода «бывает»: «jmp Begin». Я понимаю только, что .com-файл может быть 64kb, поэтому вы хотите поместить все в один сегмент. Вам нужно jmp, если вы хотите поставить переменные. Но когда я ищу об этом, многие гиды просто говорят в комментарии, что jmp Begin - это только пропустить данные и ничего больше. И вот мой вопрос: Что именно происходит в данный момент:Почему данные разобраны в инструкции?

enter image description here

Оказывается, что он работает в этом

 mov  al, a 
     mov  bl, b 
     sub  al, bl 

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

  .MODEL TINY 

Code  SEGMENT 

      ORG 100h 
      ASSUME CS:Code, DS:Code 

Start: 
       jmp  Begin 
a    EQU  20 
b    EQU  10 
c    EQU  100 
d    EQU  5 
Result   DB  ? 


Begin: 

      mov  al, a 
      mov  bl, b 
      sub  al, bl 
      mov  bl, c 
      mul  bl 
      mov  bl, d 
      div  bl    
      mov  Result, al 
      mov  ah, 4ch 
      int  21h 

Code  ENDS 
      END    Start 
+2

Вы можете поместить переменные после кода, поэтому вам не нужно будет перепрыгивать. '.com'programs начинают выполняться по адресу' 100h', который также находится там, где они загружаются (просто означает, что первая вещь должна быть инструкцией). Начальное значение 'Result' не имеет значения, потому что оно будет перезаписано (поэтому используется'? '). – Jester

+0

Я согласен с Шут, что вам не нужен прыжок. Просто поместите данные после кода. Я никогда не использовал этот шаблон для размещения данных в начале файла .com. Мусор, который вы видите в отладчике, происходит потому, что отладчик просто декодирует байты вашего скомпилированного файла .com в виде серии инструкций, и он не заботится о инструкциях и данных перехода. Он также декодирует ваши данные как код. Передовой отладчик, такой как IDA Pro, анализирует поток и учитывает возможные переходы в коде, и он обнаруживает, что ваши данные никогда не исполняются. – pasztorpisti

ответ

3

Я стараюсь дать вам объяснение.

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

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

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

Этот последовательный разбор последовательных байтов - это простой метод, который работает большую часть времени. Если вы последовательно декодируете инструкции, которые следуют друг за другом, вы можете быть почти уверены, что процессор выполнит их в этом порядке. Однако, как только вы достигнете и декодируете команду jmp, вы не можете быть уверены, что в качестве кода действуют следующие байты. Тем не менее, вы можете попытаться их декодировать в качестве инструкций, надеясь, что в середине кода не будет данных, и да, в большинстве случаев нет данных после jmp (или аналогичной инструкции потока управления), поэтому отладчики дают вам a немой разборки как «возможно полезный прогноз»). Фактически, большая часть кода обычно заполнена условными переходами и дизассемблированием байтов после них, поскольку код является очень полезной помощью отладчика. Наличие данных в середине кода после команды перехода довольно редко, мы можем рассматривать его как краевой случай.

Давайте предположим, что у вас есть простая программа .com что просто скачет по некоторым данным, а затем существует с int 20h:

jmp start 
    db 90h 
start: 
    int 20h 

Дисассемблер, вероятно, скажет вам что-то вроде следующей разборки, начиная с компенсировано 0000 :

--> 0000 eb 01  jmp short 0003 
    0002 90   nop 
    0003 cd 20  int 20h 

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

jmp start 
    db cdh 
start: 
    int 20h 

Теперь дизассемблер покажет вам:

--> 0000 eb 01  jmp short 0003 
    0002 cd cd  int cdh 
    0004 20 ...... whatever... 

Проблема заключается в том, что некоторые инструкции состоят из более чем 1 байт и отладчик не заботится байты представляют ли код или данные для вас. В приведенном выше примере, если дизассемблер последовательно разбирает байты со смещения 0000 до конца вашей программы (включая ваши данные), то ваши 1 байтовые данные будут разбираться в двухбайтную инструкцию («кражу» первого байта вашего фактического кода), поэтому следующая команда, которую пытается отлаживать отладчик, придет на смещение 0004 вместо 0003, где ваш jmp обычно прыгает. В первом примере у нас не было такой проблемы, потому что данные разобрались в 1 байтовую команду и случайно после разборки части данных вашей программы следующая команда для демонтажа для отладчика была на смещении 0003, что является точно целью вашего jmp.

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

Скажем, вы отлаживаете вторую примерную программу, и вы выполняете всю инструкцию в ней один за другим. При запуске программы с указателем инструкции == 0000 отладчик показывает это:

--> 0000 eb 01  jmp short 0003 
    0002 cd cd  int cdh 
    0004 20 ...... whatever... 

Однако, когда вы запускаете команду «шаг», чтобы выполнить одну команду указатель команд (IP) изменения в 0003 и отладчик выполняет «немая разборка» снова от смещения 0003 до экрана отладчика заполняется, так что вы увидите это:

--> 0003 cd 20  int 20h 
    0005 ...... whatever... 

Вывода: Если у вас есть тупые дизассемблеры и вы смешиваете данные в середину коды (с jmp S вокруг данные), тогда немой дизассемблер будет обрабатывать ваши данные как код, и это может привести к «второстепенному» выпуску вы столкнулись.

Продвинутый дизассемблер с анализом потока (например, Ida Pro) выполнит разборку, следуя инструкциям перехода. После разборки вашего jmp со смещением 0000 выяснилось, что следующая инструкция для демонтажа является целью jmp на 0003, и в следующем шаге она разобьет int 20h. Он будет отмечать байт db cdh со смещением 0002 в качестве данных.

Дополнительное объяснение:

Как вы уже заметили, инструкция в (Довольно устаревшее) 8086 набор инструкций может быть где-то между 1-6 байт, но jmp или call может прыгать в любом месте в памяти с байтовой зернистости. Длина команды обычно определяется из первых 1 или 2 байтов инструкции. Однако байты «склеиваются» в инструкцию только тогда, когда процессор нацеливается на первый байт команды со своим специальным IP (регистром указателя команд) и пытается выполнить байты с заданным смещением. Давайте посмотрим на хитрый пример: у вас есть байты eb ff 26 05 00 03 00 в памяти со смещением 0000, и вы выполняете его шаг за шагом.

--> 0000 eb ff  jmp short 0001 
    0002 26 05 00 03 es: add ax, 300h 
    0006 00 ...... whatever... 

Указатель инструкции процессора (IP) указывает на смещение 0000 так он декодирует команду и байты там «слипаются в инструкции» на время исполнения. (Процессор выполняет декодирование команд в 0000.) Поскольку первый байт является eb, он знает, что длина команды равна 2 байтам. Отладчик также знает об этом, поэтому он декодирует инструкцию для вас, а также генерирует некоторую дополнительную разборку с ошибкой на основе неверного предположения о том, что в какой-то момент процессор выполнит команду со смещением 0002, а затем со смещением 0006 и т. Д. Когда вы увидит, что это неверно, процессор будет склеить байты в команды с совершенно разными смещениями.

Как вы видите, мой сложный байт-код содержит jmp, который перескакивает на офсет 0001, который находится в середине выполненной команды jmp !!! Это, однако, не проблема. Процессор не заботится об этом и, к счастью, переходит к офсету 0001, поэтому на следующем шаге он попытается декодировать инструкцию (или «соединить байты») там. Давайте посмотрим, какие инструкции будет процессор найти на 0001:

--> 0001 ff 26 05 00 jmp word ptr [5] 
    0005 03 00  add ax, word ptr [bx+si] 

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

Инструкция на 0001 сообщает процессору поднять слово со смещения 0005 и интерпретировать его как смещение, чтобы прыгать туда. Как вы видите, значение word ptr [5] равно 3 (в качестве младшего 16-битного значения), поэтому процессор помещает 3 в свой IP-регистр (перескакивает до 0003). Давайте посмотрим, что он находит по смещению 0003:

--> 0003 05 00 03  add ax, 300h 

Было бы трудно показать разборку мой хитрый байт-код Е.Б. Ф.Ф. 26 05 00 03 00 в стиле отладчика, так как фактические инструкций, выполняемых процессор находятся в перекрывающихся областях памяти. Сначала процессор выполнил байты 0000-0001, затем 0001-0004 и, наконец, 0003-0005.

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

+0

Является ли самая длинная инструкция 8086 (без каких-либо избыточных префиксов) на самом деле коротким, как 6 байтов? Архитектурно, 15 байт - это верхний предел длины insn, но самая длинная не-избыточная кодировка, которую я нашел, - 14B: например. '65 67 66 47 0f 3a 0f 94 28 00 01 00 00 08 palignr xmm10, XMMWORD PTR gs: [r8d + r13d * 1 + 0x100], 0x8' (синтаксис GNU' objdump -Mintel'). Префиксы - это переопределение сегмента GS, размер адреса, размер операнда (обязательная часть кодировки 'palignr') и REX. В режиме адресации используется байт SIB и disp32, а insn имеет imm8. И это SSSE3 insn, так что это уже давно. –

+0

@PeterCordes Если мы говорим о добром старшем 16-битном 8086, а не о семействе x86 вообще, то 6 байтов без префикса верны, насколько я знаю. Я не видел 15-разрядную избыточную инструкцию, но, честно говоря, я даже не искал ее. :-D – pasztorpisti

+0

К сожалению, я оставил что-то во время редактирования: означает, что 15 байт - это верхний предел для 386 и более поздних версий, а самый большой пример 14B, который я нашел, был для x86 в целом. Он будет меньше для x86-32 (без REX, и потеряет либо размер адреса, либо байт SIB). И да, я спрашивал о 8086, а не о 80186, и определенно не о 80386. Нет ли там 8086 insns с многобайтовым кодом операции, который может принимать немедленный и операнд памяти? '81 80 34 12 78 56 добавить слово [bx + si + 0x1234], 0x5678' - 6B с однобайтовым кодом операции. –

1

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

Если у вас есть некоторые байты, которые вы не хотите, чтобы процессор выполнялся как код, просто убедитесь, что выполнение никогда не достигает их. jmp над блоками, отличными от кода, и сделать системный вызов exit до того, как выполнение провалится в некод. (Или цикл навсегда).

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

Смежные вопросы