我想在指令级别对我的C代码进行统计分析。 我需要知道我正在执行多少次加法,乘法,除法等。
这不是您通常的磨码分析要求。我是算法开发人员,我想估算将代码转换为硬件实现的成本。为此,我在运行时被问到指令调用故障(解析编译的程序集是不够的,因为它不考虑代码中的循环)。
环顾四周后,似乎VMware可能会提供一个可能的解决方案,但我仍然找不到能够跟踪我的进程的指令调用流的特定功能。
您是否了解启用此功能的任何分析工具?
答案 0 :(得分:10)
我最终使用了一个微不足道但又有效的解决方案。
display/i $pc
配置了一个简单的gdb脚本,该脚本在我需要分析的函数中断,并按指令进入步骤指令:
set $i=0
break main
run
while ($i<100000)
si
set $i = $i + 1
end
quit
使用我的脚本将输出转储到日志文件中执行gdb:
gdb -x script a.out > log.txt
分析日志以计算特定的指令调用。
原油,但它有效......
答案 1 :(得分:6)
您可以使用pin-instat这是一个PIN工具。由于它记录的信息多于指令计数,因此它有点过于杀戮。它仍然应该比你的gdb方法更有效。
免责声明:我是pin-instat的作者。
答案 2 :(得分:5)
Linux工具perf
将为您提供大量的分析信息;具体而言,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> [...]
答案 3 :(得分:4)
valgrind工具cachegrind可用于获取已编译程序集中每行的执行计数(第一列中的Ir
值)。
答案 4 :(得分:0)
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 upstream。
组装并运行:
as -o main.o main.S
ld -o main.out main.o
./main.out
标准输出:
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
我希望这种方法相对较快。它可以通过读取输入指令并生成主机可以运行的输出指令来工作,就像在https://stackoverflow.com/a/2971979/895245
中提到的cachegrind一样关于这一点的一件很酷的事情是,您还可以轻松跟踪其他体系结构的可执行文件,例如,参见aarch64:How does native android code written for ARM run on 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
但是,它不会在诸如puts之类的共享库中显示符号。
但是如果您使用-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
在Ubuntu 16.04,QEMU 2.5.0中进行了测试。