我目前正在处理一个家庭作业问题,要求我找出运行用C语言编写的简短程序时执行的机器代码指令的数量。
这个问题说我能够使用我想弄清楚的任何工具,但是我对C还是很陌生,对如何解决这个问题几乎一无所知。
我需要哪种类型的工具来解决这个问题?
答案 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
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
检查过)。
Number of executed Instructions different for Hello World program Nasm Assembly and C
@MichaelPetch的答案显示了如何使用不需要启动代码即可运行其printf
的备用libc(MUSL)。因此,您可以编译C程序并将其main
设置为ELF入口点(并调用_exit()
而不是返回)。
How can I profile C++ code running on Linux?有很多用于查找热点的分析工具和昂贵的函数(包括在调用它们的函数中花费的时间,即堆栈回溯分析)。不过,大多数情况下,这与计数指令无关。
这些是用于计数指令的重型工具,包括仅对特定种类的指令进行计数。
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)
一种执行此操作的方法可能是使用计数指令手动插入每个指令。有几种方法-
您可以修改任何开源编译器(gcc / LLVM)的指令发射器部分,以在每条指令之前发出计数指令。如果您有兴趣,我可以在LLVM中添加执行此操作的确切方法。但我相信,我在此处提供的第二种方法将更易于实现,并且可以在大多数编译器中使用。
您可以在汇编后使用说明。大多数编译器提供了生成可读程序集的选项,而不是目标文件。 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
返回。如果它调用exit
或abort
,则将看不到指令计数。您还可以提供exit
和abort
的工具版本来解决该问题。
使用基于编译器的方法,可以通过为每个基本块添加一条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 = ®s[regsidx];
if (initflg) {
ptxcmd(PTRACE_GETREGS,NULL,prev);
break;
}
regsidx = ! regsidx;
cur = ®s[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
函数返回之前打印此号码。
对于体系结构,编译器和选项的给定组合,您将获得无仪表程序为程序提供的任何输入将执行的指令数,但不包括在库函数或启动过程中执行的指令和退出阶段。