Чтение счетчика программы напрямую - PullRequest
27 голосов
/ 01 марта 2009

Можно ли считывать программный счетчик на процессорах Intel напрямую (без трюков) в режиме ядра или в каком-либо другом режиме?

Ответы [ 6 ]

33 голосов
/ 01 марта 2009

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

   mov eax, nearby_label    ; in position-dependent code
nearby_label:

Чтобы получить EIP или IP в 32-битном коде, не зависящем от позиции:

        call _here
_here:  pop eax
; eax now holds the PC.

На процессорах, более новых, чем Pentium Pro (или, возможно, PIII), call rel32 с rel32 = 0 имеет специальный случай, чтобы не влиять на стек предикторов обратных адресов . Так что это эффективно и компактно на современном x86, и это то, что clang использует для 32-битного позиционно-независимого кода.

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

get_retaddr_ppro:
    mov  eax, [esp]
    ret                ; keeps the return-address predictor stack balanced
                       ; even on CPUs where  call +0 isn't a no-op.

В режиме x86-64 RIP может считываться напрямую с использованием относительного RIP lea.

default rel           ; NASM directive: use RIP-relative by default

lea  rax, [_here]     ; RIP + 0
_here:

MASM или GNU .intel_syntax: lea rax, [rip]

Синтаксис AT & T: lea 0(%rip), %rax

27 голосов
/ 01 марта 2009

Если вам нужен адрес конкретной инструкции, обычно что-то вроде этого помогает:

thisone: 
   mov (e)ax,thisone

(Примечание: на некоторых ассемблерах это может сделать неправильно и прочитать слово из [thisone], но обычно есть некоторый синтаксис для того, чтобы заставить ассемблер делать правильные вещи.)

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

15 голосов
/ 26 июня 2009

На x86-64 вы можете сделать, например:

lea rax,[rip] (48 8d 05 00 00 00 00)
8 голосов
/ 01 марта 2009

Нет инструкции по непосредственному чтению указателя инструкций (EIP) на x86. Вы можете получить адрес текущей собираемой инструкции с помощью небольшой встроенной сборки:

// GCC inline assembler; for MSVC, syntax is different
uint32_t eip;
__asm__ __volatile__("movl $., %0", : "=r"(eip));

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

// In a C header file:
uint32_t get_eip(void);

// In a separate assembly (.S) file:
.globl _get_eip
_get_eip:
    mov 0(%esp), %eax
    ret

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

Каждый раз, когда у вас есть инструкция CALL, текущее значение EIP помещается в RAS, и каждый раз, когда у вас есть инструкция RET, прерывается RAS, и верхнее значение используется в качестве целевого предсказания перехода для этой инструкции. Если вы испортите RAS (например, не сопоставляя каждый CALL с RET, как в решении Коди ), вы получите целую кучу ненужных неправильных предсказаний ветвления, замедляющих вашу программу. Этот метод не прерывает RAS, поскольку у него есть согласованная пара команд CALL и RET.

3 голосов
/ 14 июня 2012

Существует независимый от архитектуры (но зависящий от gcc) способ доступа к адресу, который выполняется с использованием меток в качестве значений:

http://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html

void foo()
{
  void *current_address = $$current_address_label;
  current_address_label:
      ....
}
0 голосов
/ 24 ноября 2010

Вы также можете прочитать это из / proc / stat. Проверьте справочные руководства.

...