13

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

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

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

您是否知道任何启用此功能的分析工具?

4

5 回答 5

11

我最终使用了一个简单而有效的解决方案。

  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. 分析日志以计算特定指令调用。

粗制滥造,但它的工作...

于 2010-06-08T16:50:23.910 回答
6

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

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

于 2014-05-19T03:29:33.683 回答
6

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

可以使用 深入到指令级别perf annotate。为此,您需要perf annotate使用要注释的命令名称调用。所有带有样本的函数都将被反汇编,并且每条指令都将报告其相对百分比的样本:
性能记录 ./noploop 5
perf 注释 -d ./noploop

------------------------------------------------
 百分比 | noploop.noggdb的源代码&反汇编
------------------------------------------------
         :
         :
         :
         :部分.text的反汇编:
         :
         : 08048484 <主>:
    0.00 : 8048484: 55 推 %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 移动 0x2c(%esp),%eax
    0.00 : 8048536: 83 c0 01 添加 $0x1,%eax
   14.52 : 8048539: 89 44 24 2c 移动 %eax,0x2c(%esp)
   14.27 : 804853d: 8b 44 24 2c 移动 0x2c(%esp),%eax
   56.13 : 8048541: 3d ff e0 f5 05 cmp $0x5f5e0ff,%eax
    0.00 : 8048546: 76 ea jbe 8048532 <main+0xae> [...]
于 2014-05-19T03:50:06.493 回答
4

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

于 2010-06-04T06:34:17.030 回答
1

QEMU 用户模式-d in_asm

这是您可以获得指令跟踪的另一件简单的事情:

sudo apt-get install qemu-user
qemu-x86_64 -d in_asm main.out

让我们用 x86_64 Triple hello world 来测试一下:

电源

.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

标准输出:

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:为 ARM 编写的原生 android 代码如何在 x86 上运行?

此方法还显示未剥离的可执行文件的当前符号,例如:

主程序

#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 中测试。

于 2019-04-11T14:48:25.390 回答