Как определить количество машинных инструкций x86, выполняемых в программе на C? - PullRequest
0 голосов
/ 25 января 2019

В настоящее время я работаю над проблемой домашней работы, которая просит меня узнать количество инструкций машинного кода, которые выполняются при запуске короткой программы, которую я написал на C.

В вопросе говорится, что я могу использовать любые инструменты, которые я хочу выяснить, но я довольно плохо знаком с C и очень мало знаю, как это сделать.

Какие типы инструментов мне нужны, чтобы понять это?

Ответы [ 4 ]

0 голосов
/ 30 января 2019

Вы можете использовать компилятор Годболта для компиляции вашей программы и отображения кода сборки для различных компиляторов и опций.

Затем подсчитайте количество инструкций для каждого фрагмента, то есть: последовательностьоператоры вплоть до первого теста.

Затем инструмент, который вы кодируете: добавьте глобальную переменную instruction_count, инициализированную количеством команд в эпилоге функции main, и увеличьте эту переменную в начале каждого фрагментапо количеству инструкций, которые вы посчитали на предыдущем шаге.и напечатайте это число непосредственно перед возвратом из функции main.

Вы получите количество инструкций, которые будут выполнены неинструментированной программой для любого ввода данных в программу, для данной комбинации архитектуры, компилятор и опции, но не включая инструкции, выполняемые в функциях библиотеки, ни на этапах запуска, ни на выходе.

0 голосов
/ 25 января 2019

Одним из способов сделать это может быть ручная обработка каждой инструкции инструкцией подсчета.Есть несколько способов сделать это -

  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, что делает эту аппаратуру не такой уж дорогой.

Теперь для инструментирования инструкций мы используем perlas -

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.Если он вызывает exit или abort, вы не сможете увидеть количество команд.Вы также можете предоставить инструментальную версию exit и abort для решения этой проблемы.

При использовании подхода, основанного на компиляторе, это можно сделать более эффективным, добавив одну инструкцию addq для каждого базового блока, в которой параметром является номер инструкции, которую имеет BB, поскольку, как только поток управления входит в базовый блок, он обязан пройти через это.

0 голосов
/ 25 января 2019

Как я уже упоминал в моих главных комментариях, один из способов сделать это - написать программу, которая передает команды 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;
}
0 голосов
/ 25 января 2019

Терминология: вы запрашиваете динамическое количество команд.например, подсчет инструкции внутри цикла каждый раз, когда она выполняется.Обычно это примерно связано с производительностью, но количество команд за цикл может сильно различаться.

Люди также обращают внимание на static инструкцияcount (или, чаще всего, просто размер кода, потому что это действительно важно для размера кэша инструкций и времени загрузки диска).Для наборов команд переменной длины, таких как x86, они коррелированы, но это не одно и то же.На RISC с инструкциями фиксированной длины, например MIPS или AArch64, он ближе, но у вас все еще есть заполнение, например, для выравнивания начала функций.Это совершенно отдельный показатель.gcc -Os оптимизирует размер кода, стараясь не жертвовать большой скоростью.


Если вы работаете в Linux, используйте gcc -O2 foo.c для компиляции кода.-O2 не включает автоматическую векторизацию для gcc.(Это для лязг).Вероятно, это хороший базовый уровень оптимизации, который избавит вас от того, что в вашем C-коде не требуется, чтобы избежать глупых различий между использованием большего или меньшего числа переменных tmp для разбивки большого выражения.Возможно, используйте -Og, если вам нужна минимальная оптимизация, или -O0, если вам нужен действительно тупой код braindead, который компилирует каждый оператор отдельно и никогда не хранит ничего в регистрах между операторами.( Почему clang производит неэффективный asm с -O0 (для этой простой суммы с плавающей запятой)? ).

Да, это имеет значение огромная сумма как вы компилируете.gcc -O3 -march=native -ffast-math может использовать намного меньше инструкций, если он автоматически векторизует цикл.

Чтобы остановить оптимизацию кода, возьмите ввод из аргумента командной строки или прочитайте его из volatileпеременная.Как volatile int size_volatile = 1234; int size = size_volatile;.И вернуть или напечатать результат, потому что, если у программы нет побочных эффектов, то наиболее эффективная реализация - просто выйти сразу.


Затем запустите perf stat ./a.out.Он будет использовать счетчики производительности оборудования, чтобы дать вам общее количество инструкций, выполненных от имени вашего процесса, в том числе внутри ядра.(Наряду с другими счетчиками, такими как тактовые частоты ядра процессора, и некоторыми программными счетчиками, такими как page-faults и время в микросекундах.)

Для подсчета только инструкций пользовательского пространства используйте perf stat -e instructions:u ./a.out,Это все еще будет очень большое число даже для простой программы "hello world", такой как 180k, потому что она включает в себя запуск динамического компоновщика и весь код, который выполняется внутри библиотечных функций.И код запуска CRT, который вызывает ваш main, и который делает системный вызов exit с возвращаемым значением main, если вы вернетесь вместо вызова exit(3).

Вы можетестатически связать вашу C-программу, чтобы уменьшить накладные расходы при запуске, компилируя с gcc -O2 -static -fno-stack-protector -fno-pie -no-pie

perf, считая instructions:u, на моем процессоре Skylake довольно точно.Статически связанный двоичный файл x86-64, который содержит только 2 инструкции, mov eax, 231 / syscall, считается как 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.Кажется разумным для функций инициализации glibc, включая stdio, и запуск CRT перед вызовом main.(main само по себе имеет только 8 инструкций, которые я проверил с помощью objdump -drwC -Mintel a.out | less).


Другие ресурсы:


Бинарные инструменты:

Это мощные инструменты для подсчета инструкций, в том числе для подсчета только определенных видов инструкций.

  • Intel Pin - инструмент динамического бинарного инструментария
  • Эмулятор разработки программного обеспечения Intel® (SDE) Он основан на PIN-коде и удобен для таких вещей, как тестирование кода AVX512 на компьютере разработчика, который не поддерживает AVX512. (Он динамически перекомпилируется, поэтому большинство инструкций выполняется изначально, но неподдерживаемые инструкции вызывают процедуру эмуляции.)

    Например, sde64 -mix -- ./my_program напечатает комбинацию команд для вашей программы с общим количеством для каждой отдельной инструкции и разбивкой по категориям. См. libsvm, скомпилированный с AVX против AVX , для примера вида вывода.

    Он также предоставляет таблицу общего количества динамических команд для каждой функции, а также для каждого потока и глобальных. Вывод SDE mix не очень хорошо работает на исполняемом файле PIE, хотя : он считает, что динамический компоновщик является исполняемым файлом (потому что он есть), поэтому скомпилируйте с gcc -O2 -no-pie -fno-pie prog.c -o prog. Однако он все еще не видит puts вызовов или main в выводе профиля для программы тестирования Wello World, и я не знаю, почему нет.

  • Вычисление «FLOP» с помощью эмулятора разработки программного обеспечения Intel® (Intel® SDE) Пример использования SDE для подсчета определенных видов инструкций, например vfmadd231pd.

    Процессоры Intel имеют счетчики производительности HW для таких событий, как fp_arith_inst_retired.256b_packed_double, поэтому вы можете использовать их для подсчета FLOP. Они на самом деле считают FMA как 2 события . Поэтому, если у вас есть процессор Intel, который может выполнять ваш код в исходном режиме, вы можете сделать это вместо этого с помощью 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.

Это все вещи Intel; IDK, что есть у AMD, или любой другой материал для ISA, кроме x86. Это только те инструменты, о которых я слышал; Я уверен, что есть много вещей, которые я оставляю.

...