Чтение счетчика программ напрямую

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

Ответов (7)

Решение

Нет, к 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 можно читать напрямую, используя относительный RIPlea .

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

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

lea rax,[rip] (48 8d 05 00 00 00 00)

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

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

void foo()
{
  void *current_address = $$current_address_label;
  current_address_label:
      ....
}

Есть простой способ изменить счетчик программ (eip)

Когда вы вызываете функцию с помощью 'call', eip помещается в стек, а когда вы возвращаете, eip просто выталкивается из стека. Итак, все, что вам нужно сделать, это нажать желаемое значение, а затем ret. Например:

mov eax, 0x100
push eax`
ret

и дело сделано.

Нет инструкции для прямого чтения указателя инструкций (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.

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

thisone: 
   mov (e)ax,thisone

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

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

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