如何确定在C程序中执行的x86机器指令的数量?

时间:2019-01-24 21:33:43

标签: c x86 profiling isa

我目前正在处理一个家庭作业问题,要求我找出运行用C语言编写的简短程序时执行的机器代码指令的数量。

这个问题说我能够使用我想弄清楚的任何工具,但是我对C还是很陌生,对如何解决这个问题几乎一无所知。

我需要哪种类型的工具来解决这个问题?

4 个答案:

答案 0 :(得分:10)

术语:您要的是动态指令数。例如每次执行时在循环内对一条指令进行计数。这通常与性能大致相关,但是每个周期的指令可能会有很大差异。

人们还会看到的是 static 指令计数(或更通常是代码大小,因为这对于指令缓存的占用空间和磁盘加载时间确实很重要)。对于x86等可变长指令集,它们是相关的,但不是同一件事。在具有固定长度指令的RISC(例如MIPS或AArch64)上,它距离更近,但是例如,您仍然需要填充以对齐功能的开始。那是一个完全独立的指标。 gcc -Os针对代码大小进行了优化,同时尝试不牺牲太多速度。


如果您使用的是Linux,请使用gcc -O2 foo.c来编译代码。 -O2没有为gcc启用自动矢量化。 (适用于clang)。这可能是一个很好的基准优化水平,可以消除C代码中实际上不需要发生的事情,以避免使用更多或更少的tmp变量来分解一个大表达式之间的愚蠢差异。如果您需要最小的优化,则可以使用-Og;如果您想要真正的笨拙的代码,它们可以单独编译每个语句,并且永远不会在语句之间的寄存器中保留任何内容,则可以使用-O0。 (Why does clang produce inefficient asm with -O0 (for this simple floating point sum)?)。

是的,重要的是您编译的数量数量 。如果gcc -O3 -march=native -ffast-math自动对循环进行矢量化处理,则可能会使用更少的指令。

要阻止代码进行优化,请从命令行arg中获取输入,或从volatile变量中读取输入。像volatile int size_volatile = 1234; int size = size_volatile;。返回或打印结果,因为如果程序没有副作用,那么最有效的实现就是立即退出。


然后运行 perf stat ./a.out 。这将使用硬件性能计数器为您提供代表您的进程执行的全部指令,包括内核内部的指令。 (以及其他计数器,例如CPU核心时钟周期,以及一些软件计数器,例如page-faults和以毫秒为单位的时间。)

要仅计算用户空间指令,请使用perf stat -e instructions:u ./a.out 。即使对于一个简单的“ hello world”程序(例如180k),这仍然是一个很大的数目,因为这包括动态链接程序启动以及在库函数中运行的所有代码。 CRT启动代码会调用您的main,并且如果您返回而不是调用exit,则会使用main的返回值进行exit(3)系统调用。

您可以使用gcc -O2 -static -fno-stack-protector -fno-pie -no-pie

进行静态链接,以减少C程序的启动开销。

perf在我的Skylake CPU上算上instructions:u似乎很准确。仅包含2条指令mov eax, 231 / syscall的静态链接x86-64二进制文件被视为3条指令。在内核和用户模式之间的过渡中可能还需要计算一条额外的指令,但这很少。

$ perf stat -e instructions:u ./exit    # hand-written in asm to check for perf overhead

 Performance counter stats for './exit':

                 3      instructions:u                                              

       0.000651529 seconds time elapsed

调用puts两次的静态链接二进制数将被33,202 instructions:u编译为gcc -O2 -static -fno-stack-protector -fno-pie -no-pie hello.c。在调用main之前,对于glibc初始化函数(包括stdio)和CRT启动项似乎是合理的。 (main本身只有8条指令,我用objdump -drwC -Mintel a.out | less检查过)。


其他资源:


二进制检测工具:

这些是用于计数指令的重型工具,包括仅对特定种类的指令进行计数。

  • Intel Pin - A Dynamic Binary Instrumentation Tool
  • Intel® Software Development Emulator (SDE)这是基于PIN的,对于在不支持AVX512的开发机上测试AVX512代码等操作非常方便。 (它会动态地重新编译,因此大多数指令都是本地运行的,但是不受支持的指令会调用仿真例程。)

    例如, sde64 -mix -- ./my_program 将为您的程序打印指令混合,其中包含每个不同指令的总数,并按类别进行细分。有关输出类型的示例,请参见libsvm compiled with AVX vs no AVX

    它还为您提供了每个功能,每个线程和全局的动态指令总数的表格。 SDE混合输出在PIE可执行文件上效果不佳:它认为动态链接器是可执行文件(因为它是可执行文件),因此请使用gcc -O2 -no-pie -fno-pie prog.c -o prog进行编译。不过,它仍然没有在wello测试程序的配置文件输出中看到puts调用或main本身,我也不知道为什么。

  • Calculating “FLOP” using Intel® Software Development Emulator (Intel® SDE)一个使用SDE来计数某些种类的指令的示例,例如vfmadd231pd

    英特尔CPU具有用于fp_arith_inst_retired.256b_packed_double之类事件的硬件性能计数器,因此您可以使用它们来计数FLOP。他们实际上count FMA as 2 events。因此,如果您拥有可以本地运行代码的Intel CPU,则可以使用perf stat -e -e fp_arith_inst_retired.256b_packed_double,fp_arith_inst_retired.128b_packed_double,fp_arith_inst_retired.scalar_double来执行。 (和/或单精度事件。)

    但是大多数其他特定类型的指令都没有事件,只有FP数学。

这些都是英特尔的东西; AMD拥有的IDK,或者x86以外的ISA的任何东西。这些只是我听说过的工具;我确定有很多事情我会遗漏。

答案 1 :(得分:2)

一种执行此操作的方法可能是使用计数指令手动插入每个指令。有几种方法-

  1. 您可以修改任何开源编译器(gcc / LLVM)的指令发射器部分,以在每条指令之前发出计数指令。如果您有兴趣,我可以在LLVM中添加执行此操作的确切方法。但我相信,我在此处提供的第二种方法将更易于实现,并且可以在大多数编译器中使用。

  2. 您可以在汇编后使用说明。大多数编译器提供了生成可读程序集的选项,而不是目标文件。 gcc / clang的标志是-S。 对于以下程序

#include <stdio.h>
int main_real(int argc, char* argv[]) {
    printf("hello world\n");
    return 0;
}

我的编译器生成以下.s文件-

    .section    __TEXT,__text,regular,pure_instructions
    .build_version macos, 10, 14
    .globl  _main_real                  ## -- Begin function main
    .p2align    4, 0x90
_main_real:                                  ## @main_real
    .cfi_startproc
## %bb.0:
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register %rbp
    subq    $32, %rsp
    leaq    L_.str(%rip), %rax
    movl    $0, -4(%rbp)
    movl    %edi, -8(%rbp)
    movq    %rsi, -16(%rbp)
    movq    %rax, %rdi
    movb    $0, %al
    callq   _printf
    xorl    %ecx, %ecx
    movl    %eax, -20(%rbp)         ## 4-byte Spill
    movl    %ecx, %eax
    addq    $32, %rsp
    popq    %rbp
    retq
    .cfi_endproc
                                        ## -- End function
    .section    __TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
    .asciz  "hello world\n"


.subsections_via_symbols

在这里很容易看出,所有以<tab>开头而不是.的地方都是指令。

现在,我们需要一个简单的程序来查找所有此类指令并对其进行检测。您可以使用perl轻松地做到这一点。 但是在实际检测代码之前,我们必须找出适当的检测指令。这将在很大程度上取决于体系结构和目标操作系统。因此,我将提供X86_64的示例。

很明显,为什么我们需要在指令之前而不是在指令之后进行仪器操作,以便同时计算分支指令。

假设全局变量__r13_save__instruction_counter初始化为零,我们可以插入指令-

movq %r13, __r13_save(%rip)
movq __instruction_counter(%rip), %r13
leaq 1(%r13), %r13
movq %r13, __instruction_counter(%rip)
movq %r13, __r13_save(%rip)

如您所见,我们使用了rip相对寻址模式,该模式对于初学者编写的大多数程序都适用(较大的程序可能会有问题)。 我们在这里使用leaq而不是incq来避免破坏程序用于控制流的标志。 (如@PeterCordes在评论中所建议。)

由于我们使用全局计数器作为指令并存放%r13寄存器,因此该工具也可以正确地用于单线程程序。为了将以上内容扩展为多线程程序,将不得不使用线程本地存储并检测线程创建功能。

此外,变量__r13_save__instruction_counter经常被访问,并且应始终位于L1高速缓存中,从而使该工具的开销不那么大。

现在要说明我们将perl用作-的指令

cat input.s | perl -pe 's/^(\t[^.])/\tmovq %r13, __r13_save(%rip)\n\tmovq __instruction_counter(%rip), %r13\n\tleaq 1(%r13), %r13\n\tmovq %r13, __instruction_counter(%rip)\n\tmovq %r13, __r13_save(%rip)\n\1/' > output.s

对于上面的示例程序,它会生成

    .section    __TEXT,__text,regular,pure_instructions
    .build_version macos, 10, 14
    .globl  _main_real              ## -- Begin function main_real
    .p2align    4, 0x90
_main_real:                             ## @main_real
    .cfi_startproc
## %bb.0:
    movq %r13, __r13_save(%rip)
    movq __instruction_counter(%rip), %r13
    leaq 1(%r13), %r13
    movq %r13, __instruction_counter(%rip)
    movq %r13, __r13_save(%rip)
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset %rbp, -16
    movq %r13, __r13_save(%rip)
    movq __instruction_counter(%rip), %r13
    leaq 1(%r13), %r13
    movq %r13, __instruction_counter(%rip)
    movq %r13, __r13_save(%rip)
    movq    %rsp, %rbp
    .cfi_def_cfa_register %rbp
    movq %r13, __r13_save(%rip)
    movq __instruction_counter(%rip), %r13
    leaq 1(%r13), %r13
    movq %r13, __instruction_counter(%rip)
    movq %r13, __r13_save(%rip)
    subq    $32, %rsp
    movq %r13, __r13_save(%rip)
    movq __instruction_counter(%rip), %r13
    leaq 1(%r13), %r13
    movq %r13, __instruction_counter(%rip)
    movq %r13, __r13_save(%rip)
    leaq    L_.str(%rip), %rax
    movq %r13, __r13_save(%rip)
    movq __instruction_counter(%rip), %r13
    leaq 1(%r13), %r13
    movq %r13, __instruction_counter(%rip)
    movq %r13, __r13_save(%rip)
    movl    %edi, -4(%rbp)
    movq %r13, __r13_save(%rip)
    movq __instruction_counter(%rip), %r13
    leaq 1(%r13), %r13
    movq %r13, __instruction_counter(%rip)
    movq %r13, __r13_save(%rip)
    movq    %rsi, -16(%rbp)
    movq %r13, __r13_save(%rip)
    movq __instruction_counter(%rip), %r13
    leaq 1(%r13), %r13
    movq %r13, __instruction_counter(%rip)
    movq %r13, __r13_save(%rip)
    movq    %rax, %rdi
    movq %r13, __r13_save(%rip)
    movq __instruction_counter(%rip), %r13
    leaq 1(%r13), %r13
    movq %r13, __instruction_counter(%rip)
    movq %r13, __r13_save(%rip)
    movb    $0, %al
    movq %r13, __r13_save(%rip)
    movq __instruction_counter(%rip), %r13
    leaq 1(%r13), %r13
    movq %r13, __instruction_counter(%rip)
    movq %r13, __r13_save(%rip)
    callq   _printf
    movq %r13, __r13_save(%rip)
    movq __instruction_counter(%rip), %r13
    leaq 1(%r13), %r13
    movq %r13, __instruction_counter(%rip)
    movq %r13, __r13_save(%rip)
    xorl    %ecx, %ecx
    movq %r13, __r13_save(%rip)
    movq __instruction_counter(%rip), %r13
    leaq 1(%r13), %r13
    movq %r13, __instruction_counter(%rip)
    movq %r13, __r13_save(%rip)
    movl    %eax, -20(%rbp)         ## 4-byte Spill
    movq %r13, __r13_save(%rip)
    movq __instruction_counter(%rip), %r13
    leaq 1(%r13), %r13
    movq %r13, __instruction_counter(%rip)
    movq %r13, __r13_save(%rip)
    movl    %ecx, %eax
    movq %r13, __r13_save(%rip)
    movq __instruction_counter(%rip), %r13
    leaq 1(%r13), %r13
    movq %r13, __instruction_counter(%rip)
    movq %r13, __r13_save(%rip)
    addq    $32, %rsp
    movq %r13, __r13_save(%rip)
    movq __instruction_counter(%rip), %r13
    leaq 1(%r13), %r13
    movq %r13, __instruction_counter(%rip)
    movq %r13, __r13_save(%rip)
    popq    %rbp
    movq %r13, __r13_save(%rip)
    movq __instruction_counter(%rip), %r13
    leaq 1(%r13), %r13
    movq %r13, __instruction_counter(%rip)
    movq %r13, __r13_save(%rip)
    retq
    .cfi_endproc
                                        ## -- End function
    .section    __TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
    .asciz  "hello world\n"


.subsections_via_symbols

现在,我们还需要在某个地方创建此变量。这可以通过将简单的c wrapper.c创建为-

来完成。
#include <stdio.h>
long long int __instruction_counter;
long long int __r13_save;
int main_real(int, char* []);
int main(int argc, char* argv[]) {
    int ret = main_real(argc, argv);
    printf("Total instructions = %lld\n", __instruction_counter);
    return ret;
}

您可能会看到函数main_real。因此,在您的实际程序中,您必须创建一个main_real而不是main

最后将所有内容链接为-

clang output.s wrapper.c -o a.out

并执行您的程序。您的代码应正常运行,并在退出之前打印指令计数。

您可能需要处理__instruction_counter变量的名称处理。对于某些ABI,编译器会在开头添加一个额外的_。在这种情况下,您将不得不在perl命令中添加一个额外的_。您还可以通过为包装器生成程序集来检查变量的确切名称。

运行上面的示例后,我得到-

hello world
Total instructions = 15

与我们的函数具有的确切指令数匹配。 您可能已经注意到,这仅计算您编写和编译的代码中的指令数量。例如,不在printf函数中。用静态仪器通常很难解决这个问题。

这里的一个警告是您的程序必须“正常”退出,即通过从main返回。如果它调用exitabort,则将看不到指令计数。您还可以提供exitabort的工具版本来解决该问题。

使用基于编译器的方法,可以通过为每个基本块添加一条addq指令(参数为BB拥有的指令数)来提高效率,因为一旦控制流进入基本块,它一定会经历它。

答案 2 :(得分:2)

正如我在最上面的评论中提到的那样,一种实现方法是编写一个程序来向gdb提供命令。

具体来说,si命令(步骤ISA指令)。

我无法使用它来处理管道,但是我可以通过将gdb放在伪tty下来使其工作。

编辑:考虑之后,我想到了一个直接在目标程序上使用ptrace的版本,而不是向gdb发送命令。它快得多 [快100倍],并且[可能]更可靠


因此,这是基于gdb的控制程序。请注意,它必须与-lutil链接。

// gdbctl -- gdb control via pseudo tty

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <fcntl.h>
#include <errno.h>
#include <poll.h>
#include <pty.h>
#include <utmp.h>
#include <sys/types.h>
#include <sys/wait.h>

int opt_d;                              // 1=show debug output
int opt_e;                              // 1=echo gdb output
int opt_f;                              // 1=set line buffered output
int opt_x;                              // si repetition factor

int zpxlvl;                             // current trace level

int ptypar;                             // parent PTY fd
int ptycld;                             // child PTY fd
char name[100];                         // child PTY device name

unsigned long long sicount;             // single step count

const char *gdb = "(gdb) ";             // gdb's prompt string
const char *waitstr;                    // currently active "wait for" string
char *waitstop[8] = { NULL };           // string that shows run is done

int stopflg;                            // 1=waitstop seen
char sicmd[100];

char waitbuf[10000];                    // large buffer to scan for strings
char *waitdst = waitbuf;                // current end position

pid_t pidgdb;                           // gdb's pid
pid_t pidfin;                           // stop pid
int status;                             // gdb's final status
double tvelap;                          // start time

#ifndef _USE_ZPRT_
#define _USE_ZPRT_      1
#endif

static inline int
zprtok(int lvl)
{

    return (_USE_ZPRT_ && (opt_d >= lvl));
}

#define dbg(_lvl,_fmt...) \
    do { \
        if (zprtok(_lvl)) \
            printf(_fmt); \
    } while (0)

// tvgetf -- get high precision time
double
tvgetf(void)
{
    struct timespec ts;
    double sec;

    clock_gettime(CLOCK_REALTIME,&ts);
    sec = ts.tv_nsec;
    sec /= 1e9;
    sec += ts.tv_sec;

    return sec;
}

// xstrcat -- concatenate a string
char *
xstrcat(char *dst,const char *src)
{
    int chr;

    for (chr = *src++;  chr != 0;  chr = *src++)
        *dst++ = chr;

    *dst = 0;

    return dst;
}

// gdbexit -- check for gdb termination
void
gdbexit(void)
{

    // NOTE: this should _not_ happen
    do {
        if (pidgdb == 0)
            break;

        pidfin = waitpid(pidgdb,&status,WNOHANG);
        if (pidfin == 0)
            break;

        pidgdb = 0;
        printf("gdbexit: WAITPID status=%8.8X\n",status);
        exit(8);
    } while (0);
}

// gdbwaitpoll -- wait for prompt string
void
gdbwaitpoll(const char *buf)
{
    char *cp;
    char **wstr;

    do {
        gdbexit();

        if (waitstr == NULL)
            break;

        // concatenate to big buffer
        dbg(2,"BUF '%s'\n",buf);
        waitdst = xstrcat(waitdst,buf);

        // check for final termination string (e.g. "exited with")
        for (wstr = waitstop;  *wstr != NULL;  ++wstr) {
            cp = *wstr;
            dbg(2,"TRYSTOP '%s'\n",cp);
            cp = strstr(waitbuf,cp);
            if (cp != NULL) {
                stopflg = 1;
                waitstop[0] = NULL;
            }
        }

        // check for the prompt (e.g. "(gdb) ")
        cp = strstr(waitbuf,waitstr);
        if (cp == NULL)
            break;

        dbg(1,"HIT on '%s'\n",waitstr);

        // got it reset things
        waitbuf[0] = 0;
        waitdst = waitbuf;
        waitstr = NULL;
    } while (0);
}

// gdbrecv -- process input from gdb
void
gdbrecv(void)
{
    struct pollfd fds[1];
    struct pollfd *fd = &fds[0];
    int xlen;
    char buf[1000];

    fd->fd = ptypar;
    fd->events = POLLIN;

    while (1) {
        gdbexit();

#if 1
        int nfd = poll(fds,1,1);
        if (nfd <= 0) {
            if (waitstr != NULL)
                continue;
            break;
        }
#endif

        // get a chunk of data
        xlen = read(ptypar,buf,sizeof(buf) - 1);
        dbg(1,"gdbrecv: READ xlen=%d\n",xlen);

        if (xlen < 0) {
            printf("ERR: %s\n",strerror(errno));
            break;
        }

        // wait until we've drained every bit of data
        if (xlen == 0) {
            if (waitstr != NULL)
                continue;
            break;
        }

        // add EOS char
        buf[xlen] = 0;

        dbg(1,"ECHO: ");
        if (opt_e)
            fwrite(buf,1,xlen,stdout);

        // wait for our prompt
        gdbwaitpoll(buf);
    }
}

// gdbwaitfor -- set up prompt string to wait for
void
gdbwaitfor(const char *wstr,int loopflg)
{

    waitstr = wstr;
    if (waitstr != NULL)
        dbg(1,"WAITFOR: '%s'\n",waitstr);

    while ((waitstr != NULL) && loopflg && (pidgdb != 0))
        gdbrecv();
}

// gdbcmd -- send command to gdb
void
gdbcmd(const char *str,const char *wstr)
{
    int rlen = strlen(str);
    int xlen = 0;

#if 0
    printf("CMD/%d: %s",rlen,str);
#endif

    gdbwaitfor(wstr,0);

    for (;  rlen > 0;  rlen -= xlen, str += xlen) {
        gdbexit();

        xlen = write(ptypar,str,rlen);
        if (xlen <= 0)
            break;

        dbg(1,"RET: rlen=%d xlen=%d\n",rlen,xlen);
        gdbrecv();
    }

    dbg(1,"END/%d\n",xlen);
}

// gdbctl -- control gdb
void
gdbctl(int argc,char **argv)
{

    // this is the optimal number for speed
    if (opt_x < 0)
        opt_x = 100;

    if (opt_x <= 1) {
        opt_x = 1;
        sprintf(sicmd,"si\n");
    }
    else
        sprintf(sicmd,"si %d\n",opt_x);

    // create pseudo TTY
    openpty(&ptypar,&ptycld,name,NULL,NULL);

    pidgdb = fork();

    // launch gdb
    if (pidgdb == 0) {
        //sleep(1);

        login_tty(ptycld);
        close(ptypar);

        char *gargs[8];
        char **gdst = gargs;

        *gdst++ = "gdb";
        *gdst++ = "-n";
        *gdst++ = "-q";
        *gdst++ = *argv;
        *gdst = NULL;

        execvp(gargs[0],gargs);
        exit(9);
    }

    // make input from gdb non-blocking
#if 1
    int flags = fcntl(ptypar,F_GETFL,0);
    flags |= O_NONBLOCK;
    fcntl(ptypar,F_SETFL,flags);
#endif

    // wait
    char **wstr = waitstop;
    *wstr++ = "exited with code";
    *wstr++ = "Program received signal";
    *wstr++ = "Program terminated with signal";
    *wstr = NULL;

    printf("TTY: %s\n",name);
    printf("SI: %d\n",opt_x);
    printf("GDB: %d\n",pidgdb);

#if 1
    sleep(2);
#endif

    gdbwaitfor(gdb,1);

    // prevent kill or quit commands from hanging
    gdbcmd("set confirm off\n",gdb);

    // set breakpoint at earliest point
#if 1
    gdbcmd("b _start\n",gdb);
#else
    gdbcmd("b main\n",gdb);
#endif

    // skip over target program name
    --argc;
    ++argv;

    // add extra arguments
    do {
        if (argc <= 0)
            break;

        char xargs[1000];
        char *xdst = xargs;
        xdst += sprintf(xdst,"set args");

        for (int avidx = 0;  avidx < argc;  ++avidx, ++argv) {
            printf("XARGS: '%s'\n",*argv);
            xdst += sprintf(xdst," %s",*argv);
        }

        xdst += sprintf(xdst,"\n");

        gdbcmd(xargs,gdb);
    } while (0);

    // run the program -- it will stop at the breakpoint we set
    gdbcmd("run\n",gdb);

    // disable the breakpoint for speed
    gdbcmd("disable\n",gdb);

    tvelap = tvgetf();

    while (1) {
        // single step an ISA instruction
        gdbcmd(sicmd,gdb);

        // check for gdb aborting
        if (pidgdb == 0)
            break;

        // check for target program exiting
        if (stopflg)
            break;

        // advance count of ISA instructions
        sicount += opt_x;
    }

    // get elapsed time
    tvelap = tvgetf() - tvelap;

    // tell gdb to quit
    gdbcmd("quit\n",NULL);

    // wait for gdb to completely terminate
    if (pidgdb != 0) {
        pidfin = waitpid(pidgdb,&status,0);
        pidgdb = 0;
    }

    // close PTY units
    close(ptypar);
    close(ptycld);
}

// main -- main program
int
main(int argc,char **argv)
{
    char *cp;

    --argc;
    ++argv;

    for (;  argc > 0;  --argc, ++argv) {
        cp = *argv;
        if (*cp != '-')
            break;

        switch (cp[1]) {
        case 'd':
            cp += 2;
            opt_d = (*cp != 0) ? atoi(cp) : 1;
            break;

        case 'e':
            cp += 2;
            opt_e = (*cp != 0) ? atoi(cp) : 1;
            break;

        case 'f':
            cp += 2;
            opt_f = (*cp != 0) ? atoi(cp) : 1;
            break;

        case 'x':
            cp += 2;
            opt_x = (*cp != 0) ? atoi(cp) : -1;
            break;
        }
    }

    if (argc == 0) {
        printf("specify target program\n");
        exit(1);
    }

    // set output line buffering
    switch (opt_f) {
    case 0:
        break;

    case 1:
        setlinebuf(stdout);
        break;

    default:
        setbuf(stdout,NULL);
        break;
    }

    gdbctl(argc,argv);

    // print statistics
    printf("%llu instructions -- ELAPSED: %.9f -- %.3f insts / sec\n",
        sicount,tvelap,(double) sicount / tvelap);

    return 0;
}

这是一个示例测试程序:

// tgt -- sample slave/test program

#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

int opt_S;

int glob;

void
dumb(int x)
{

    glob += x;
}

int
spin(int lim)
{
    int x;

    for (x = 0;  x < lim;  ++x)
        dumb(x);

    return x;
}

int
main(int argc,char **argv)
{
    char *cp;
    int lim;
    int *ptr;
    int code;

    --argc;
    ++argv;

    for (;  argc > 0;  --argc, ++argv) {
        cp = *argv;
        if (*cp != '-')
            break;

        switch (cp[1]) {
        case 'S':
            opt_S = cp[2];
            break;
        }
    }

    switch (opt_S) {
    case 'f':  // cause segfault
        ptr = NULL;
        *ptr = 23;
        code = 91;
        break;

    case 'a':  // abort
        abort();
        code = 92;
        break;

    case 't':  // terminate us
        signal(SIGTERM,SIG_DFL);
#if 0
        kill(getpid(),SIGTERM);
#else
        raise(SIGTERM);
#endif
        code = 93;
        break;

    default:
        code = 0;
        break;
    }

    if (argc > 0)
        lim = atoi(argv[0]);
    else
        lim = 10000;

    lim = spin(lim);
    lim &= 0x7F;
    if (code == 0)
        code = lim;

    return code;
}

这是使用ptrace的版本,比使用gdb的版本要快很多:

// ptxctl -- control via ptrace

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
//#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <sys/user.h>

int opt_d;                              // 1=show debug output
int opt_e;                              // 1=echo progress
int opt_f;                              // 1=set line buffered output

unsigned long long sicount;             // single step count

int stopflg;                            // 1=stop seen

pid_t pidtgt;                           // gdb's pid
pid_t pidfin;                           // stop pid

int status;                             // target's final status
char statbuf[1000];                     // status buffer
int coredump;                           // 1=core dumped

int zpxlvl;                             // current trace level

int regsidx;                            // regs index
struct user_regs_struct regs[2];        // current regs

#define REGSALL(_cmd) \
    _cmd(r15) \
    _cmd(r14) \
    _cmd(r13) \
    _cmd(r12) \
    _cmd(rbp) \
    _cmd(rbx) \
    _cmd(r11) \
    _cmd(r10) \
    _cmd(r9) \
    _cmd(r8) \
    _cmd(rax) \
    _cmd(rcx) \
    _cmd(rdx) \
    _cmd(rsi) \
    _cmd(rdi) \
    _cmd(orig_rax) \
    /*_cmd(rip)*/ \
    _cmd(cs) \
    _cmd(eflags) \
    _cmd(rsp) \
    _cmd(ss) \
    _cmd(fs_base) \
    _cmd(gs_base) \
    _cmd(ds) \
    _cmd(es) \
    _cmd(fs) \
    _cmd(gs)

#define REGSDIF(_reg) \
    if (cur->_reg != prev->_reg) \
        printf("  %16.16llX " #_reg "\n",cur->_reg);

double tvelap;                          // start time

#ifndef _USE_ZPRT_
#define _USE_ZPRT_      1
#endif

static inline int
zprtok(int lvl)
{

    return (_USE_ZPRT_ && (opt_d >= lvl));
}

#define dbg(_lvl,_fmt...) \
    do { \
        if (zprtok(_lvl)) \
            printf(_fmt); \
    } while (0)

// tvgetf -- get high precision time
double
tvgetf(void)
{
    struct timespec ts;
    double sec;

    clock_gettime(CLOCK_REALTIME,&ts);
    sec = ts.tv_nsec;
    sec /= 1e9;
    sec += ts.tv_sec;

    return sec;
}

// ptxstatus -- decode status
char *
ptxstatus(int status)
{
    int zflg;
    int signo;
    char *bp;

    bp = statbuf;
    *bp = 0;

    // NOTE: do _not_ use zprtok here -- we need to force this on final
    zflg = (opt_d >= zpxlvl);

    do {
        if (zflg)
            bp += sprintf(bp,"%8.8X",status);

        if (WIFSTOPPED(status)) {
            signo = WSTOPSIG(status);
            if (zflg)
                bp += sprintf(bp," WIFSTOPPED signo=%d",signo);

            switch (signo) {
            case SIGTRAP:
                break;
            default:
                stopflg = 1;
                break;
            }
        }

        if (WIFEXITED(status)) {
            if (zflg)
                bp += sprintf(bp," WIFEXITED code=%d",WEXITSTATUS(status));
            stopflg = 1;
        }

        if (WIFSIGNALED(status)) {
            signo = WTERMSIG(status);
            if (zflg)
                bp += sprintf(bp," WIFSIGNALED signo=%d",signo);

            if (WCOREDUMP(status)) {
                coredump = 1;
                stopflg = 1;
                if (zflg)
                    bp += sprintf(bp," -- core dumped");
            }
        }
    } while (0);

    return statbuf;
}

// ptxcmd -- issue ptrace command
long
ptxcmd(enum __ptrace_request cmd,void *addr,void *data)
{
    long ret;

    dbg(zpxlvl,"ptxcmd: ENTER cmd=%d addr=%p data=%p\n",cmd,addr,data);
    ret = ptrace(cmd,pidtgt,addr,data);
    dbg(zpxlvl,"ptxcmd: EXIT ret=%ld\n",ret);

    return ret;
}

// ptxwait -- wait for target to be stopped
void
ptxwait(const char *reason)
{

    dbg(zpxlvl,"ptxwait: %s pidtgt=%d\n",reason,pidtgt);
    pidfin = waitpid(pidtgt,&status,0);

    // NOTE: we need this to decide on stop status
    ptxstatus(status);

    dbg(zpxlvl,"ptxwait: %s status=(%s) pidfin=%d\n",
        reason,statbuf,pidfin);
}

// ptxwhere -- show where we are
void
ptxwhere(int initflg)
{
    struct user_regs_struct *cur;
    struct user_regs_struct *prev;

    do {
        prev = &regs[regsidx];

        if (initflg) {
            ptxcmd(PTRACE_GETREGS,NULL,prev);
            break;
        }

        regsidx = ! regsidx;
        cur = &regs[regsidx];

        ptxcmd(PTRACE_GETREGS,NULL,cur);
        printf("RIP: %16.16llX (%llu)\n",cur->rip,sicount);

        if (opt_e < 2)
            break;

        REGSALL(REGSDIF);
    } while (0);
}

// ptxctl -- control ptrace
void
ptxctl(int argc,char **argv)
{

    pidtgt = fork();

    // launch target program
    if (pidtgt == 0) {
        pidtgt = getpid();
        ptxcmd(PTRACE_TRACEME,NULL,NULL);
        execvp(argv[0],argv);
        exit(9);
    }

#if 0
    sleep(1);
#endif

    zpxlvl = 1;

#if 0
    ptxwait("SETUP");
#endif

    // attach to tracee
    // NOTE: we do _not_ need to do this because child has done TRACEME
#if 0
    dbg(zpxlvl,"ptxctl: PREATTACH\n");
    ptxcmd(PTRACE_ATTACH,NULL,NULL);
    dbg(zpxlvl,"ptxctl: POSTATTACH\n");
#endif

    // wait for initial stop
#if 1
    ptxwait("INIT");
#endif

    if (opt_e)
        ptxwhere(1);

    dbg(zpxlvl,"ptxctl: START\n");

    tvelap = tvgetf();

    zpxlvl = 2;

    while (1) {
        dbg(zpxlvl,"ptxctl: SINGLESTEP\n");
        ptxcmd(PTRACE_SINGLESTEP,NULL,NULL);
        ptxwait("WAIT");

        sicount += 1;

        // show where we are
        if (opt_e)
            ptxwhere(0);

        dbg(zpxlvl,"ptxctl: STEPCOUNT sicount=%lld\n",sicount);

        // stop when target terminates
        if (stopflg)
            break;
    }

    zpxlvl = 0;
    ptxstatus(status);
    printf("ptxctl: STATUS (%s) pidfin=%d\n",statbuf,pidfin);

    // get elapsed time
    tvelap = tvgetf() - tvelap;
}

// main -- main program
int
main(int argc,char **argv)
{
    char *cp;

    --argc;
    ++argv;

    for (;  argc > 0;  --argc, ++argv) {
        cp = *argv;
        if (*cp != '-')
            break;

        switch (cp[1]) {
        case 'd':
            cp += 2;
            opt_d = (*cp != 0) ? atoi(cp) : 1;
            break;

        case 'e':
            cp += 2;
            opt_e = (*cp != 0) ? atoi(cp) : 1;
            break;

        case 'f':
            cp += 2;
            opt_f = (*cp != 0) ? atoi(cp) : 1;
            break;
        }
    }

    if (argc == 0) {
        printf("specify target program\n");
        exit(1);
    }

    // set output line buffering
    switch (opt_f) {
    case 0:
        break;

    case 1:
        setlinebuf(stdout);
        break;

    default:
        setbuf(stdout,NULL);
        break;
    }

    ptxctl(argc,argv);

    // print statistics
    printf("%llu instructions -- ELAPSED: %.9f -- %.3f insts / sec\n",
        sicount,tvelap,(double) sicount / tvelap);

    return 0;
}

答案 3 :(得分:0)

您可以使用Godbolt's Compiler Explorer来编译程序并显示各种编译器和选项的汇编代码。

然后计算每个片段的指令数量,即:直到第一个测试并包括第一个测试的语句序列。

然后编写代码的工具:添加一个全局变量const date = new Date(); //get todays date but care about month and day only let client_date = new Date(2018, date.getMonth() , date.getDate()); let data = parsed_data.filter(value => { const start_date_parts = value['begin'].split('-'); const end_date_parts = value['end'].split('-'); const start_date = new Date(start_date_parts[0], start_date_parts[1]-1, start_date_parts[2]); const end_date = new Date(end_date_parts[0], end_date_parts[1]-1, end_date_parts[2]); // compare now client_date if (client_date >= start_date && client_date <= end_date){ return value; } }); ,并初始化为instruction_count函数Epilog中的指令数,并在每个片段的开头将此变量增加您计数的指令数在上一步中。并在从main函数返回之前打印此号码。

对于体系结构,编译器和选项的给定组合,您将获得无仪表程序为程序提供的任何输入将执行的指令数,但不包括在库函数或启动过程中执行的指令和退出阶段。