Чтение счетчика программы напрямую
Можно ли считать счетчик программ на процессорах Intel напрямую (то есть без "хитростей") в режиме ядра или в каком-либо другом режиме?
6 ответов
Нет, прямой доступ к 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
Если вам нужен адрес конкретной инструкции, обычно что-то вроде этого делает свое дело:
thisone:
mov (e)ax,thisone
(Примечание: на некоторых ассемблерах это может сделать неправильно и прочитать слово из [thisone], но обычно есть некоторый синтаксис для того, чтобы ассемблер сделал правильную вещь.)
Если ваш код статически загружен на определенный адрес, ассемблер уже знает (если вы указали правильный начальный адрес) абсолютные адреса всех инструкций. Динамически загружаемый код, скажем, как часть приложения в любой современной ОС, получит правильный адрес благодаря перемещению адресов, выполняемому динамическим компоновщиком (при условии, что ассемблер достаточно умен, чтобы генерировать таблицы перемещения, которыми они обычно являются).
На x86-64 вы можете сделать, например:
lea rax,[rip] (48 8d 05 00 00 00 00)
На x86 нет инструкции для непосредственного чтения указателя инструкций (EIP). Вы можете получить адрес текущей собираемой инструкции с помощью небольшой встроенной сборки:
// 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.
Существует независимый от архитектуры (но зависящий от gcc) способ доступа к адресу, который выполняется с использованием меток в качестве значений:
http://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html
void foo()
{
void *current_address = $$current_address_label;
current_address_label:
....
}
Вы также можете прочитать это из /proc/stat. Проверьте страницы руководства.
Есть простой способ изменить счетчик программ (eip)
Когда вы вызываете функцию с помощью 'call', eip помещается в стек, а когда вы возвращаете, eip просто выталкивается из стека. Итак, все, что вам нужно сделать, это нажать желаемое значение и затем ret. например:
mov eax, 0x100
push eax`
ret
и дело сделано.