Имеет ли счетчик программ текущий адрес или адрес следующей инструкции? - PullRequest
0 голосов
/ 25 августа 2018

Будучи новичком и самообучающимся, я изучаю ассемблер и сейчас читаю глава 3 книги «Компаньон С» Аллена Холлуба. Я не могу понять описание счетчика программ или ПК он описывает в воображаемой демонстрационной машине с двухбайтовым словом. Вот описание ПК на стр. 57.

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

Я не могу понять разницу между удержанием адреса current и адреса инструкции next . Содержит ли ПК два адреса одновременно в двух последовательных байтах?

Ответы [ 3 ]

0 голосов
/ 26 августа 2018

Эти утверждения могут говорить о двух разных моментах времени: во время против после исполнения инструкции.

Что было в тех [...], которые вы пропустили? Говорил ли он о завершении выполнения одной инструкции и начале извлечения следующей инструкции после увеличения ПК на 2 байта / 1 слово инструкции?

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

Я не понимаю разницу между удержанием текущего адреса и адреса следующей инструкции

Рассмотрим эти (x86) инструкции в памяти, используя 2-байтовые инструкции для соответствия ISA из вашей книги (инструкции x86 имеют переменную длину от 1 до 15 байтов, включая байты необязательного / обязательного префикса):

 a:  0x66 0x90     nop
 c:  0x66 0x90     nop

Каждая инструкция имеет свой адрес. Я указал их начальные адреса шестнадцатеричными цифрами (которые также могут быть символическими метками в синтаксисе ассемблера, но это должен быть макет вывода дизассемблера, например objdump -d). «Адрес инструкции» - это адрес ее первого байта в памяти, независимо от того, что архитектурный ПК будет держать до / во время / после выполнения.

Пока выполняется первый nop, адрес следующей инструкции - c. «Текущая инструкция» является первой nop, независимо от того, какое значение ПК (логически) имеет во время выполнения.


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

MIPS и RISC-V также / вместо этого имеют aupc инструкции, которые добавляют регистр или непосредственно к счетчику программ и помещают результат в другой регистр. Таким образом, вместо режима относительной адресации к ПК у них есть относительный к ПК add, чтобы создать указатель, который можно использовать в качестве режима адресации. Но такая же разница, правда.

Пока существует согласованное правило для логического значения ПК во время выполнения инструкции, на самом деле не имеет значения, какое именно правило.

  • PC = начало текущей инструкции (например, MIPS логически работает таким образом, независимо от того, что на самом деле делают внутренние реализации).

    Относительные ветви MIPS равны относительно PC + 4 (т.е. относительно следующей инструкции, поэтому для этой цели это всего лишь причуда того, как она документирована), но переходы MIPS заменяют младшие 28 бит ПК, не ПК + 4 (который потенциально отличается своими старшими битами). См. Также http://www.cim.mcgill.ca/~langer/273/13-datapath1.pdf, в котором описана логическая операция извлечения / выполнения команды в MIPS.)

  • ПК = начало следующей инструкции (обычно, например, x86)
  • ПК = начало 2 инструкции позже. (например, ARM)

    Почему регистр ARM PC указывает на инструкцию после следующей, которая должна быть выполнена? TL: DR: артефакт 3-этапного интерфейса конвейера выборки-декодирования-выполнения в раннем ARM конструкций. (32-битный ARM выставляет счетчик программ как r15, один из 16 регистров общего назначения, так что вы можете фактически перейти с or pc, r0, #4 или чем-то еще, а также прочитать его в любой инструкции для относительной адресации ПК) .

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

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

Правила обработки исключений будут отделены от обычных правил ПК во время выполнения, поэтому аппаратное обеспечение должно помнить длину инструкций или начальный адрес инструкции, чтобы иметь возможность обрабатывать исключения. Оно не должно быть эффективным , потому что прерывания / исключения редки; нормально, чтобы процессор выполнял несколько циклов, прежде чем он даже перейдет к обработчику прерываний. (Случай нормальной работы режимов относительной адресации ПК и call инструкций должен быть эффективным.)


Последствия простой физической реализации с ПК = текущая инструкция

Наличие ПК с адресом текущей инструкции является допустимым вариантом.

Для суперскалярной конвейерной конструкции, особенно в исполнении Out-of-Order, это не имеет большого значения. Конвейер должен отслеживать адрес (и длину, если переменная) каждой инструкции, когда она проходит через конвейер, потому что он может извлекать / декодировать / выполнять более 1 за цикл. Он извлекается большими блоками и декодирует до n инструкций из этого блока. Например, в некоторых реализациях блоки выборки могут быть выровнены по 16 байтов. (См. https://agner.org/optimize/ для получения подробной информации о том, как различные микроархитектуры x86 делают это и как оптимизировать для шаблонов выборки / декодирования переднего плана в Pentium, Pentium Pro, Nehalem и т. Д. К счастью, современные процессоры x86 имеют декодированные кэши uop-uop и гораздо менее чувствительны к проблемам извлечения / декодирования в циклах.)

(Полусвязанные: x86 регистры: MBR / MDR и регистры команд modern)

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

В x86 IP / EIP / RIP логически содержит адрес инструкции next , пока выполняется текущая инструкция. Это имеет смысл, учитывая его происхождение в 8086 году, в котором было всего ~ 29 тыс. Транзисторов. Он предварительно выбирается из потока команд во время выполнения текущего insn (в небольшой 6-байтовый буфер, который даже не достаточно длинный, чтобы содержать целую инструкцию, если используются дополнительные префиксы, но который содержит 6 однобайтовых инструкций). Но он даже не начал декодировать следующий, пока текущий не был закончен. (т. е. не конвейеризован вообще, или, возможно, двухэтапный, если считать предварительную выборку, которую очень легко отделить. Я думаю, это продолжалось до 486).

С ISA переменной длины длина инструкции не обнаруживается до декодирования. Наличие PC = конец текущей инструкции может иметь большее значение, потому что вы не можете просто рассчитать PC + 4, как MIPS, или PC + 2 с вашей игрушкой ISA. Но вы также не сможете вернуться назад, если не знаете длину инструкции, поэтому для правильной обработки исключений 8086 также должен был отслеживать начало команды или запоминать длину инструкции.

0 голосов
/ 09 ноября 2018

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

2000: 0b 12        push r11     
2002: 3b 40 21 00  mov #33, r11
2006: 3b 41        pop r11      
2008: 30 41        ret  

Как уже упоминалось, когда речь идет о счетчике программ, существует понятие времени.

Можно подумать о таком сверхпростом процессоре, старом 8-битном и других, новые же отличаются.

Когда мы вводим этот код, как бы мы здесь ни находились, это не имеет значения. Счетчик программ 0x2000 Это говорит нам, где выбрать инструкцию, которую мы должны получить, декодировать, а затем выполнить, повторить.

Это 16-битные инструкции, два байта, процессор начинает извлекать, когда компьютер указывает на инструкцию, то есть адрес инструкции. Процессор считывает два байта один по адресу 0x2000 (0x0b), процессор увеличивает счетчик программы до 0x2001 и использует его для извлечения второй половины инструкции по адресу 0x2001 (0x12) и увеличивает счетчик программы до 0x2002. Таким образом, для каждой выборки в этом, давайте назовем это составленным процессором, который я описываю для каждой выборки, которую вы выбираете, используя счетчик программ в качестве адреса, затем увеличивайте счетчик программ.

before data after
0x2000 0x0b 0x2001
0x2001 0x12 0x2002

Итак, теперь мы декодируем инструкцию, счетчик программы в настоящее время показывает 0x2002, мы видим, что это push r11, поэтому мы переходим к выполнению.

Во время выполнения этой инструкции счетчик программы остается равным 0x2002. Значение регистра r11 помещается в стек.

Теперь мы начинаем получать следующую инструкцию.

before data after
0x2002 0x3b 0x2003
0x2003 0x40 0x2004

Когда мы декодируем эту инструкцию (pc == 0x2004) mov # немедленное, r11 процессор понимает, что для этой инструкции требуется немедленное выполнение, поэтому ему нужно получить еще два байта

before data after
0x2004 0x21 0x2005
0x2005 0x00 0x2006

Он определил, что теперь может выполнять инструкцию (младший порядок 0x0021 = 33 десятичного), записав значение 0x0021 в регистр r11. Во время выполнения счетчик программы для этой инструкции равен 0x2006.

следующий

before data after
0x2006 0x3b 0x2007
0x2007 0x41 0x2008

декодировать и выполнить поп-код r11

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

наконец

before data after
0x2008 0x30 0x2009
0x2009 0x41 0x200A

декодирует ret, теперь этот является особенным для вопроса, потому что ret собирается изменить счетчик программы во время выполнения согласно правилам этого процессора. Если команда, которая назвала адрес 0x2000, была, скажем, 0x1000, и это была двухбайтовая инструкция, то после выборки и во время декодирования счетчик программы будет находиться по адресу 0x1002, во время выполнения адрес 0x1002 будет храниться где-то в соответствии с правилами этого набора команд и Программный счетчик примет значение 0x2000 для вызова этой подпрограммы. Когда мы добираемся до инструкции ret и начинаем ее выполнять, мы начинаем выполнение ret со счетчика программы, содержащего 0x200A, но ret помещает адрес инструкции после вызова, значение сохраняется во время выполнения вызова, поэтому при В конце этой инструкции счетчик программы будет содержать значение 0x1002, и следующая выборка будет с этого адреса.

Так что в этой последней инструкции перед выполнением ПК указывает на то, чтокак правило, следующая инструкция для инструкций, которые не ветвятся или прыгают вызов. 0x200A. Но во время выполнения счетчик программы был изменен так, чтобы «следующая» инструкция - та, что после вызова, которая привела нас сюда.

еще немного

c064:   0a 24           jz  $+22        ;abs 0xc07a
c066:   4e 5e           rla.b   r14     

перед загрузкой компьютера 0xC064. после выборки и декодирования ПК равен 0xC066. Инструкция гласит: переходите к 0xc07a. Таким образом, если нулевой флаг не установлен, компьютер остается на 0xC066, и именно здесь он начинает следующую инструкцию, но если z установить затем ПК изменяется на 0xc07a, и именно там следующая инструкция выполнить будет. Так что до 0xc064 после 0xc066 или 0xc07a в зависимости.

После одной инструкции - до следующей.

безусловный прыжок

c074:   c2 4d 21 00     mov.b   r13,    &0x0021 
c078:   ee 3f           jmp $-34        ;abs 0xc056

до получения 0xc07a, до выполнения 0xc07A после выполнения 0xc056

для этой одной инструкции ПК содержал как минимум три значения (при извлечении байта в то время он удерживал 0xc078, 0xc079, 0xc07a и заканчивался 0xc056) в течение одна инструкция.

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

0 голосов
/ 25 августа 2018

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

Он описывает простой процессор, который объясняет как работают процессоры в целом .

Реальные процессоры гораздо более сложны:

Во многих руководствах (для любого типа процессора) вы найдете предложения типа: «Регистр ПК помещается в стек.»

Обычно это означает, что адрес инструкции, которая выполняется после возврата из инструкции call, помещается в стек.

Однако такие предложения не являются на 100% правильными: в случае процессора 68k (см. Ниже) записывается адрес следующей инструкции, а не инструкция текущей инструкции плюс 2!

Для большинства процессоров PC-относительные jump инструкции относятся к адресу следующей инструкции ;однако существуют контрпримеры (такие как PowerPC VLE).

32-битные x86-процессоры (как используется в большинстве настольных компьютеров / ноутбуков)

На таких процессорах,только call непосредственно считывает регистр EIP , и только инструкции по переходу записывают EIP.Этой «изоляции» достаточно, чтобы этот регистр представлял собой какую-то внутреннюю схему в ЦП, если вообще существует физический регистр EIP, и вы не обязательно знаете его содержимое.

(Вы можете посчитать int инструкции, такие как int3 или int 0x80, которые также читают CS: EIP, потому что они должны выдвигать фрейм исключений, но имеет больше смысла думать о них как о механизме обработки исключений.

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

(относительные переходы с ПК относительно адреса следующей инструкции.)

64-битные процессоры x86

В этих процессорах есть инструкции, которые напрямую используют регистр RIP, например, mov eax,[rip+symbol_offset] для выполнения ПКреклама статических данных;делает позиционно-независимый код для разделяемых библиотек и ASLR значительно более эффективным, чем 32-битный x86.В этом случае «RIP» является адресом следующей инструкции .

68k

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

Поскольку такие инструкции имеют длину как минимум 4 байта, значение ПКрегистр будет отображать адрес байта " в середине " инструкции.

ARM

При чтении ПК на процессорах ARM (его можно прочитать напрямую!) значение обычно отражает адрес текущей инструкции плюс 8 , в некоторых случаях даже плюс 12!

(инструкции длиной 4 байта, поэтому "текущая инструкция плюс8 "означает: адрес две инструкции впереди!)

...