Инструкции по трассировке/профилированию
Я хотел бы статистически профилировать свой код C на уровне инструкций.
Мне нужно знать, сколько дополнений, умножений, делений и т.д. Я выполняю.
Это не обычный запуск требования профилирования кода фрезы. Я разработчик алгоритмов, и я хочу оценить стоимость преобразования моего кода в аппаратные реализации. Для этого меня спрашивают о разрыве вызова команды во время выполнения (синтаксический анализ скомпилированной сборки недостаточен, поскольку он не учитывает циклы в коде).
Посмотрев вокруг, кажется, что VMware может предложить возможное решение, но я все еще не мог найти определенную функцию, которая позволит мне отслеживать поток вызовов команд моего процесса.
Знаете ли вы о каких-либо инструментах профилирования, которые позволяют это?
Ответы
Ответ 1
В конечном итоге я использовал тривиальное, но эффективное решение.
- Конфигурированный GDB, чтобы отобразить разборку следующей инструкции (каждый раз, когда она останавливается), вызывая:
display/i $pc
-
Настроил простой gdb script, который разбивается на функцию, которую мне нужно проанализировать, и переходит к инструкции по инструкции:
set $i=0
break main
run
while ($i<100000)
si
set $i = $i + 1
end
quit
-
Выполненный gdb с моим script выводом вывода в файл журнала:
gdb -x script a.out > log.txt
-
Проанализировал журнал для подсчета вызовов определенных команд.
Сырой, но он работает...
Ответ 2
Вы можете использовать pin-instat, который является инструментом PIN-кода. Это немного перебивает, так как записывает больше информации, чем счетчик команд. Это все еще должно быть более эффективным, чем ваш подход к GDB.
Отказ от ответственности: я автор пин-инстата.
Ответ 3
perf
инструментов Linux даст вам много информации для профилирования; в частности, perf annotate
даст вам относительное количество команд на каждую инструкцию.
Можно развернуть до уровня инструкций с perf annotate
. Для этого вам нужно вызвать perf annotate
с именем команды для аннотирования. Все функции с образцами будут разобраны, и в каждой инструкции будет указан относительный процент образцов: perf record ./noploop 5
perf annotate -d ./noploop
------------------------------------------------
Percent | Source code & Disassembly of noploop.noggdb
------------------------------------------------
:
:
:
: Disassembly of section .text:
:
: 08048484 <main>:
0.00 : 8048484: 55 push %ebp
0.00 : 8048485: 89 e5 mov %esp,%ebp [...]
0.00 : 8048530: eb 0b jmp 804853d <main+0xb9>
15.08 : 8048532: 8b 44 24 2c mov 0x2c(%esp),%eax
0.00 : 8048536: 83 c0 01 add $0x1,%eax
14.52 : 8048539: 89 44 24 2c mov %eax,0x2c(%esp)
14.27 : 804853d: 8b 44 24 2c mov 0x2c(%esp),%eax
56.13 : 8048541: 3d ff e0 f5 05 cmp $0x5f5e0ff,%eax
0.00 : 8048546: 76 ea jbe 8048532 <main+0xae> [...]
Ответ 4
Инструмент valgrind cachegrind может использоваться для получения счетчиков выполнения каждой строки в скомпилированной сборке (значение Ir
в первом столбец).
Ответ 5
Пользовательский режим QEMU -d in_asm
Это еще одна простая вещь, которую вы можете сделать, чтобы получить след команды:
sudo apt-get install qemu-user
qemu-x86_64 -d in_asm main.out
Давайте проверим это с тройным привет миром x86_64:
main.S
.text
.global _start
_start:
asm_main_after_prologue:
mov $3, %rbx
write:
mov $1, %rax /* syscall number */
mov $1, %rdi /* stdout */
mov $msg, %rsi /* buffer */
mov $len, %rdx /* len */
syscall
dec %rbx
jne write
exit:
mov $60, %rax /* syscall number */
mov $0, %rdi /* exit status */
syscall
msg:
.ascii "hello\n"
len = . - msg
адаптировано из GitHub вверх по течению.
Собрать и запустить с:
as -o main.o main.S
ld -o main.out main.o
./main.out
Вывод Stdout:
hello
hello
hello
Запуск его через QEMU выводит трассировку команд в stderr:
warning: TCG doesn't support requested feature: CPUID.01H:ECX.vmx [bit 5]
host mmap_min_addr=0x10000
Reserved 0x1000 bytes of guest address space
Relocating guest address space from 0x0000000000400000 to 0x400000
guest_base 0x0
start end size prot
0000000000400000-0000000000401000 0000000000001000 r-x
0000004000000000-0000004000001000 0000000000001000 ---
0000004000001000-0000004000801000 0000000000800000 rw-
start_brk 0x0000000000000000
end_code 0x00000000004000b8
start_code 0x0000000000400000
start_data 0x00000000004000b8
end_data 0x00000000004000b8
start_stack 0x00000040007fed70
brk 0x00000000004000b8
entry 0x0000000000400078
----------------
IN:
0x0000000000400078: mov $0x3,%rbx
0x000000000040007f: mov $0x1,%rax
0x0000000000400086: mov $0x1,%rdi
0x000000000040008d: mov $0x4000b2,%rsi
0x0000000000400094: mov $0x6,%rdx
0x000000000040009b: syscall
----------------
IN:
0x000000000040009d: dec %rbx
0x00000000004000a0: jne 0x40007f
----------------
IN:
0x000000000040007f: mov $0x1,%rax
0x0000000000400086: mov $0x1,%rdi
0x000000000040008d: mov $0x4000b2,%rsi
0x0000000000400094: mov $0x6,%rdx
0x000000000040009b: syscall
----------------
IN:
0x00000000004000a2: mov $0x3c,%rax
0x00000000004000a9: mov $0x0,%rdi
0x00000000004000b0: syscall
Я ожидаю, что этот метод будет относительно быстрым. Он работает путем чтения инструкций ввода и выдачи инструкций вывода, которые может запустить хост, так же, как cachegrind, о котором упоминалось по адресу: fooobar.com/info/465814/...
Еще одна интересная вещь - это то, что вы также можете тривиально отслеживать исполняемые файлы других архитектур, см., Например, aarch64: Как собственный код Android, написанный для ARM, работает на x86?
Этот метод также отображает текущий символ неиспользуемых исполняемых файлов, например, след:
main.c
#include <stdio.h>
int say_hello() {
puts("hello");
}
int main(void) {
say_hello();
}
скомпилируйте и запустите:
gcc -ggdb3 -O0 -o main.out main.c
qemu-x86_64 -d in_asm ./main.out
содержит:
----------------
IN: main
0x0000000000400537: push %rbp
0x0000000000400538: mov %rsp,%rbp
0x000000000040053b: mov $0x0,%eax
0x0000000000400540: callq 0x400526
----------------
IN: say_hello
0x0000000000400526: push %rbp
0x0000000000400527: mov %rsp,%rbp
0x000000000040052a: mov $0x4005d4,%edi
0x000000000040052f: callq 0x400400
----------------
IN:
0x0000000000400400: jmpq *0x200c12(%rip) # 0x601018
Однако он не показывает символы в общих библиотеках, таких как put.
Но вы можете увидеть их, если скомпилируете с -static
:
----------------
IN: main
0x00000000004009bf: push %rbp
0x00000000004009c0: mov %rsp,%rbp
0x00000000004009c3: mov $0x0,%eax
0x00000000004009c8: callq 0x4009ae
----------------
IN: say_hello
0x00000000004009ae: push %rbp
0x00000000004009af: mov %rsp,%rbp
0x00000000004009b2: mov $0x4a1064,%edi
0x00000000004009b7: callq 0x40faa0
----------------
IN: puts
0x000000000040faa0: push %r12
0x000000000040faa2: push %rbp
0x000000000040faa3: mov %rdi,%r12
0x000000000040faa6: push %rbx
0x000000000040faa7: callq 0x423830
Связанный: https://unix.stackexchange.com/info/147343/how-to-determine-what-instructions-a-process-is-executing
Протестировано в Ubuntu 16.04, QEMU 2.5.0.