我想创建一个程序的执行的完整指令跟踪,收集一些统计等。我第一次尝试使用linux'ptrace功能来完成一个程序(使用教程here)。这创建了两个进程,追踪的和调试器,并通过信号进行通信。我每秒只有16K条指令(在1.6GHz的Atom上),所以对于任何不重要的东西来说这太慢了。试图单步通过程序与陷阱国旗和陷阱信号处理程序,崩溃vsyscall
我认为通过信号的进程间通信太慢了,所以我尝试在执行的同一个进程中设置调试:设置陷阱标志,并创建一个信号处理程序。当使用软件中断进行系统调用时,应该保存陷阱标志,内核将使用它自己的标志 - 所以我想。但是我的程序以某种方式被信号SIGTRAP杀死。
这是我设置:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int cycle = 0;
void trapHandler(int signum) {
if (cycle % 262144 == 0) {
write(STDOUT_FILENO," trap\n",6);
}
cycle += 1;
}
void startTrace() {
// set up signal handler
signal(SIGTRAP, trapHandler);
// set trap flag
asm volatile("pushfl\n"
"orl $0x100, (%esp)\n"
"popfl\n"
);
}
void printRock() {
char* s = "Rock\n";
asm(
"movl $5, %%edx\n" // message length
"movl %0, %%ecx\n" // message to write
"movl $1, %%ebx\n" // file descriptor (stdout)
"movl $4, %%eax\n" // system call number (sys_write)
"int $0x80\n" // sycall
: // no output regs
: "r"(s) // input text
: "edx","ecx","ebx","eax"
);
}
int main() {
startTrace();
// some computation
int x = 0;
int i;
for (i = 0; i < 100000; i++) {
x += i*2;
}
printRock();
write(STDOUT_FILENO,"Paper\n",6);
write(STDOUT_FILENO,"Scissors\n",9);
}
运行时,这给:
trap
trap
trap
Rock
Paper
trap
Trace/breakpoint trap (core dumped)
所以,现在我们得到约每秒250K的指令,仍然缓慢,但不平凡的执行是可能的。但是在这两个写入调用之间似乎会发生核心转储。在GDB中,我们看到它发生在哪里:
Dump of assembler code for function __kernel_vsyscall:
0xb76f3414 <+0>: push %ecx
0xb76f3415 <+1>: push %edx
0xb76f3416 <+2>: push %ebp
0xb76f3417 <+3>: mov %esp,%ebp
0xb76f3419 <+5>: sysenter
0xb76f341b <+7>: nop
0xb76f341c <+8>: nop
0xb76f341d <+9>: nop
0xb76f341e <+10>: nop
0xb76f341f <+11>: nop
0xb76f3420 <+12>: nop
0xb76f3421 <+13>: nop
0xb76f3422 <+14>: int $0x80
=> 0xb76f3424 <+16>: pop %ebp
0xb76f3425 <+17>: pop %edx
0xb76f3426 <+18>: pop %ecx
0xb76f3427 <+19>: ret
而且回溯:
Program terminated with signal SIGTRAP, Trace/breakpoint trap.
#0 0xb77c5424 in __kernel_vsyscall()
#1 0xb76d0553 in __write_nocancel() at ../sysdeps/unix/syscall-template.S:81
#2 0x0804847d in trapHandler (signum=5) at count.c:8
#3 <signal handler called>
#4 0xb77c5424 in __kernel_vsyscall()
#5 0xb76d0553 in __write_nocancel() at ../sysdeps/unix/syscall-template.S:81
#6 0x08048537 in main() at count.c:49
看来通过int 80
碰巧都很好的系统调用,但写调用使用内核的VIDSO/vsyscall某种程度上打破(我不知道这个功能,更接近here)。它可能与使用sysenter
而不是int 80
有关,也可能在进入内核时陷阱标志存活。我不太清楚递归调用__kernel_vsyscall
的情况。我也不明白为什么在__kernel_vsyscall
函数中有int 80
的调用。
有没有人有建议发生了什么,以及如何解决这个问题?也许可以禁用VDSO/vsysicall?或者是否可以使用int 80
而不是sysenter
替代__kernel_vsyscall
函数?