Одним из способов сделать это может быть ручная обработка каждой инструкции инструкцией подсчета.Есть несколько способов сделать это -
Вы можете изменить часть генератора команд любого компилятора с открытым исходным кодом (gcc / LLVM), чтобы она выдавала команду подсчета перед каждой инструкцией.Я могу добавить к ответу точный способ сделать это в LLVM, если вам интересно.Но я полагаю, что второй метод, который я здесь привожу, будет легче реализовать и будет работать с большинством компиляторов.
Вы можете использовать инструкции после компиляции.Большинство компиляторов предоставляют возможность генерировать читаемую сборку вместо объектных файлов.Флаг для gcc / clang: -S
.Для следующей программы
#include <stdio.h>
int main_real(int argc, char* argv[]) {
printf("hello world\n");
return 0;
}
мой компилятор создает следующий файл .s
-
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 10, 14
.globl _main_real ## -- Begin function main
.p2align 4, 0x90
_main_real: ## @main_real
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
subq $32, %rsp
leaq L_.str(%rip), %rax
movl $0, -4(%rbp)
movl %edi, -8(%rbp)
movq %rsi, -16(%rbp)
movq %rax, %rdi
movb $0, %al
callq _printf
xorl %ecx, %ecx
movl %eax, -20(%rbp) ## 4-byte Spill
movl %ecx, %eax
addq $32, %rsp
popq %rbp
retq
.cfi_endproc
## -- End function
.section __TEXT,__cstring,cstring_literals
L_.str: ## @.str
.asciz "hello world\n"
.subsections_via_symbols
Здесь легко увидеть, что все, что начинается с <tab>
, за которым не следует .
, является инструкцией.
Теперь нам нужна простая программа, которая находит все такие инструкции и инструктирует их.Вы можете сделать это легко с perl
.Но прежде чем мы на самом деле применяем код, мы должны найти подходящую инструкцию.Это будет во многом зависеть от архитектуры и целевой операционной системы.Поэтому я приведу пример для X86_64.
Понятно, почему мы должны инструктировать ПЕРЕД инструкциями, а не ПОСЛЕ их, чтобы также посчитать команды ветвления.
Принимая глобальные переменные __r13_save
и __instruction_counter
, инициализированные дляноль, мы можем вставить инструкцию -
movq %r13, __r13_save(%rip)
movq __instruction_counter(%rip), %r13
leaq 1(%r13), %r13
movq %r13, __instruction_counter(%rip)
movq %r13, __r13_save(%rip)
Как вы можете видеть, мы использовали режим относительной адресации rip
, который подходит для большинства программ, которые пишет новичок (большие программы могут иметь проблемы),Мы использовали leaq
здесь вместо incq
, чтобы избежать засорения флагов, которые используются программой для потока управления.(Как предложено @PeterCordes в комментариях.)
Этот инструментарий также работает правильно для однопоточных программ, поскольку мы используем глобальный счетчик для инструкций и скрываем регистр %r13
.Для расширения вышеперечисленного для многопоточной программы нужно будет использовать локальное хранилище потока и также использовать функции создания потока.
Кроме того, переменные __r13_save
и __instruction_counter
часто доступны и должны всегда находиться в кеше L1, что делает эту аппаратуру не такой уж дорогой.
Теперь для инструментирования инструкций мы используем perlas -
cat input.s | perl -pe 's/^(\t[^.])/\tmovq %r13, __r13_save(%rip)\n\tmovq __instruction_counter(%rip), %r13\n\tleaq 1(%r13), %r13\n\tmovq %r13, __instruction_counter(%rip)\n\tmovq %r13, __r13_save(%rip)\n\1/' > output.s
Для вышеприведенного примера программы это генерирует
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 10, 14
.globl _main_real ## -- Begin function main_real
.p2align 4, 0x90
_main_real: ## @main_real
.cfi_startproc
## %bb.0:
movq %r13, __r13_save(%rip)
movq __instruction_counter(%rip), %r13
leaq 1(%r13), %r13
movq %r13, __instruction_counter(%rip)
movq %r13, __r13_save(%rip)
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %r13, __r13_save(%rip)
movq __instruction_counter(%rip), %r13
leaq 1(%r13), %r13
movq %r13, __instruction_counter(%rip)
movq %r13, __r13_save(%rip)
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
movq %r13, __r13_save(%rip)
movq __instruction_counter(%rip), %r13
leaq 1(%r13), %r13
movq %r13, __instruction_counter(%rip)
movq %r13, __r13_save(%rip)
subq $32, %rsp
movq %r13, __r13_save(%rip)
movq __instruction_counter(%rip), %r13
leaq 1(%r13), %r13
movq %r13, __instruction_counter(%rip)
movq %r13, __r13_save(%rip)
leaq L_.str(%rip), %rax
movq %r13, __r13_save(%rip)
movq __instruction_counter(%rip), %r13
leaq 1(%r13), %r13
movq %r13, __instruction_counter(%rip)
movq %r13, __r13_save(%rip)
movl %edi, -4(%rbp)
movq %r13, __r13_save(%rip)
movq __instruction_counter(%rip), %r13
leaq 1(%r13), %r13
movq %r13, __instruction_counter(%rip)
movq %r13, __r13_save(%rip)
movq %rsi, -16(%rbp)
movq %r13, __r13_save(%rip)
movq __instruction_counter(%rip), %r13
leaq 1(%r13), %r13
movq %r13, __instruction_counter(%rip)
movq %r13, __r13_save(%rip)
movq %rax, %rdi
movq %r13, __r13_save(%rip)
movq __instruction_counter(%rip), %r13
leaq 1(%r13), %r13
movq %r13, __instruction_counter(%rip)
movq %r13, __r13_save(%rip)
movb $0, %al
movq %r13, __r13_save(%rip)
movq __instruction_counter(%rip), %r13
leaq 1(%r13), %r13
movq %r13, __instruction_counter(%rip)
movq %r13, __r13_save(%rip)
callq _printf
movq %r13, __r13_save(%rip)
movq __instruction_counter(%rip), %r13
leaq 1(%r13), %r13
movq %r13, __instruction_counter(%rip)
movq %r13, __r13_save(%rip)
xorl %ecx, %ecx
movq %r13, __r13_save(%rip)
movq __instruction_counter(%rip), %r13
leaq 1(%r13), %r13
movq %r13, __instruction_counter(%rip)
movq %r13, __r13_save(%rip)
movl %eax, -20(%rbp) ## 4-byte Spill
movq %r13, __r13_save(%rip)
movq __instruction_counter(%rip), %r13
leaq 1(%r13), %r13
movq %r13, __instruction_counter(%rip)
movq %r13, __r13_save(%rip)
movl %ecx, %eax
movq %r13, __r13_save(%rip)
movq __instruction_counter(%rip), %r13
leaq 1(%r13), %r13
movq %r13, __instruction_counter(%rip)
movq %r13, __r13_save(%rip)
addq $32, %rsp
movq %r13, __r13_save(%rip)
movq __instruction_counter(%rip), %r13
leaq 1(%r13), %r13
movq %r13, __instruction_counter(%rip)
movq %r13, __r13_save(%rip)
popq %rbp
movq %r13, __r13_save(%rip)
movq __instruction_counter(%rip), %r13
leaq 1(%r13), %r13
movq %r13, __instruction_counter(%rip)
movq %r13, __r13_save(%rip)
retq
.cfi_endproc
## -- End function
.section __TEXT,__cstring,cstring_literals
L_.str: ## @.str
.asciz "hello world\n"
.subsections_via_symbols
Теперь нам также нужно где-то создать эту переменную.Это можно сделать, создав простой файл c wrapper.c как -
#include <stdio.h>
long long int __instruction_counter;
long long int __r13_save;
int main_real(int, char* []);
int main(int argc, char* argv[]) {
int ret = main_real(argc, argv);
printf("Total instructions = %lld\n", __instruction_counter);
return ret;
}
Вы можете увидеть функцию main_real
.Таким образом, в вашей реальной программе вы должны создать main_real
вместо main
.
Наконец связать все как -
clang output.s wrapper.c -o a.out
и выполнить вашу программу.Ваш код должен работать нормально и печатать счетчик команд до его завершения.
Возможно, вам придется позаботиться о искажении имени переменной __instruction_counter
.Для некоторых ABI компилятор добавляет дополнительные _
в начале.В этом случае вам придется добавить дополнительный _
к команде perl.Вы можете проверить точное имя переменной, также сгенерировав сборку для оболочки.
При запуске приведенного выше примера я получаю -
hello world
Total instructions = 15
, который соответствует точному количеству инструкций нашей функцииесть.Возможно, вы заметили, что это учитывает только количество инструкций в коде, который вы написали и скомпилировали.Например, отсутствует в функции printf
.Как правило, это сложная проблема для статических приборов.
Одно предостережение в том, что ваша программа должна выйти «нормально», то есть, вернувшись из main
.Если он вызывает exit
или abort
, вы не сможете увидеть количество команд.Вы также можете предоставить инструментальную версию exit
и abort
для решения этой проблемы.
При использовании подхода, основанного на компиляторе, это можно сделать более эффективным, добавив одну инструкцию addq
для каждого базового блока, в которой параметром является номер инструкции, которую имеет BB, поскольку, как только поток управления входит в базовый блок, он обязан пройти через это.