我想在指令级别对我的C代码进行统计分析。我需要知道我正在执行多少次加法,乘法,除法等。
这不是您通常的磨码分析要求。我是算法开发人员,我想估算将代码转换为硬件实现的成本。为此,我被问到运行时的指令调用故障(解析编译的程序集是不够的,因为它不考虑代码中的循环)。
环顾四周后,似乎VMware可能提供了一个可能的解决方案,但我仍然找不到能够跟踪我的进程的指令调用流的特定功能。
你知道任何能够实现这一目标的分析工具吗?
我最终使用了一个微不足道但又有效的解决方案。
display/i $pc
set $i=0
break main
run
while ($i<100000)
si
set $i = $i + 1
end
quit
gdb -x script a.out > log.txt
原油,但它的工作原理......
您可以使用pin-instat这是一个PIN工具。它有点过于杀戮,因为它记录的信息多于指令数。它仍然应该比你的gdb方法更有效。
免责声明:我是pin-instat的作者。
Linux工具perf
将为您提供大量的分析信息;具体而言,perf annotate
将为您提供每指令相对计数。
It is possible to drill down to the instruction level withperf annotate
. For that, you need to invokeperf annotate
with the name of the command to annotate. All the functions with samples will be disassembled and each instruction will have its relative percentage of samples reported: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> [...]
valgrind工具cachegrind可用于获取已编译程序集中每行的执行计数(第一列中的Ir
值)。
QEMU用户模式-d in_asm
这是获取指令跟踪的另一个简单方法:
sudo apt-get install qemu-user
qemu-x86_64 -d in_asm main.out
让我们用x86_64三重问候世界来测试它:
电源
.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
我希望这种方法相对较快。它的工作原理是读取输入指令并生成主机可以运行的输出指令,就像在下面提到的cachegrind:https://stackoverflow.com/a/2971979/895245
关于这一点的一个很酷的事情是,您还可以轻松跟踪其他体系结构的可执行文件,例如参见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中测试。