跟踪/分析说明

问题描述 投票:13回答:5

我想在指令级别对我的C代码进行统计分析。我需要知道我正在执行多少次加法,乘法,除法等。

这不是您通常的磨码分析要求。我是算法开发人员,我想估算将代码转换为硬件实现的成本。为此,我被问到运行时的指令调用故障(解析编译的程序集是不够的,因为它不考虑代码中的循环)。

环顾四周后,似乎VMware可能提供了一个可能的解决方案,但我仍然找不到能够跟踪我的进程的指令调用流的特定功能。

你知道任何能够实现这一目标的分析工具吗?

c assembly profiling instructions
5个回答
10
投票

我最终使用了一个微不足道但又有效的解决方案。

  1. 配置GDB通过调用显示下一条指令的反汇编(每次停止):

display/i $pc

  1. 配置了一个简单的gdb脚本,它打破了我需要分析的函数并按指令继续执行步骤:
    set $i=0
    break main
    run
    while ($i<100000)
    si
    set $i = $i + 1
    end
    quit
    
  2. 用我的脚本转储输出到日志文件中执行gdb: gdb -x script a.out > log.txt
  3. 分析日志以计算特定的指令调用。

原油,但它的工作原理......


6
投票

您可以使用pin-instat这是一个PIN工具。它有点过于杀戮,因为它记录的信息多于指令数。它仍然应该比你的gdb方法更有效。

免责声明:我是pin-instat的作者。


5
投票

Linux工具perf将为您提供大量的分析信息;具体而言,perf annotate将为您提供每指令相对计数。

It is possible to drill down to the instruction level with perf annotate. For that, you need to invoke perf 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> [...]

4
投票

valgrind工具cachegrind可用于获取已编译程序集中每行的执行计数(第一列中的Ir值)。


0
投票

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

相关:https://unix.stackexchange.com/questions/147343/how-to-determine-what-instructions-a-process-is-executing

在Ubuntu 16.04,QEMU 2.5.0中测试。

© www.soinside.com 2019 - 2024. All rights reserved.