Example Implementation for ptrace The system call ptrace allows a parent process the control over one of its child processes which must be created be the system call fork. The synopsis of the call is: int ptrace (int request, int pid, int addr, int data); The child process is usually executed normally, until it receives a signal. It then stops and informs the parent process about its actual state by the system call wait. The controlling process may then read or write the child process' memory by using ptrace. The parent can also force the child to either resume or stop execution. However, the most interesting request for timing analysis is PTRACE_SINGLESTEP. This command sets the trace-bit within the child's processor-state-word and enables the parent process to trace the child's machine instructions. In this case, the child stops after every machine instruction execution and sends a signal to its parent. An example program for tracing machine instructions could be implemented as follows: #include #incldue #include #include #include void main (void) { long long counter = 1; // machine instruction counter int wait_val; // child's return value int pid; // child's process id switch (pid=fork()) { case -1: perror("fork"); break; case 0: // child process starts ptrace(0,0,0,0); // must be called in order to allow the // control over the child process execl("./testprog","testprog",NULL); // executes the testprogram and causes // the child to stop and send a signal // to the parent, the parent can now // switch to PTRACE_SINGLESTEP break; // child process ends default: // parent process starts wait(&wait_val); // parent waits for child to stop (execl) if (ptrace(PTRACE_SINGLESTEP,pid,0,0) != 0) perror("ptrace"); // switch to singlestep tracing and // release child wait(&wait_val); // wait for child to stop at next instruction while (wait_val == 1407) { counter++; if (ptrace(PTRACE_SINGLESTEP,pid,0,0) != 0) perror("ptrace"); wait(&wait_val); } // continue to stop, wait and release until // the child is finished; wait_val != 1407 // Low=0177L and High=05 (SIGTRAP) } // end of switch cout << "Counter: `` << counter << endl; } // end of main In order to provide an efficient machine instruction counting, the test program should be linked statically, like: c++ -static testprog.cpp. Hence, setting up and dynamically linking the run-time libraries, which is ``computationally expensive'', is avoided for each run of the test program. A Genetic Algorithm must execute the fitness function many times. Hereby, the test program's parameters must be passed into the fitness function and the resulting fitness must be passed back. The latter does not create any particular problems, as the machine instructions are counted within the GA procedure (parent process). But passing parameters into a process in order to call the test program (function within the process) requires some additional installations in the form of Inter Process Communication (IPC). A simple methodology for IPC is provided by pipes. A Unix pipe is a data stream which can be accessed as a file. Consequently, the calling GA process can open a pipe and write its data (parameter) into it. The called process inherits all of the parent's file descriptors and is able to read the data from the pipe. The called process may finally execute the actual test program. The GA fitness function can be implemented as follows: double fitness (int size, unsigned char* parameter_string, char* exename) { int pid; // child's process id int pfd[2]; // pipe descriptor char str_size[10]; // parameter 1 for executable char str_pfd[10]; // parameter 2 for executable if (pipe(pfd) == -1) perror("pipe"); // create pipe switch (pid=fork()) { // create new process case 0: { // child process ostrstream os1 (str_size,10); // stream on string ostrstream os2 (str_pfd,10); // for strcat if (close(pfd[1]) == -1) perror("close"); os1 << size; os2 << pfd[0]; ptrace(0,0,0,0); execlp(exename,exename,str_size,str_pfd,NULL); break; } default: // parent process if (close(pfd[0]) == -1) perror("close"); if (write(pfd[1],parameter_string,size) == -1) perror("write"); wait(&wait_val); if ptrace(PTRACE_SINGLESTEP,pid,0,0) != 0) perror("ptrace"); wait(&wait_val); while (wait_val == 1407) { counter++; if (ptrace(PTRACE_SINGLESTEP,pid,0,0) != 0) perror("ptrace"); wait(&wait_val); } return((double)counter); } // end of switch } // end of fitness The test program must access the pipe which was created by the parent process beforehand in order to retrieve the test procedure's input parameter string. It must finally execute the actual test procedure. The source code can be implemented as shown below: int main (int argc, char* argv[]) { int size; // size of the parameter string int pfd[2]; // pipe descriptor unsigned char* buffer; // buffer for pipe input istrstream is1 (argv[1],10); istrstream is2 (argv[2],10); is1 >> size; is2 >> pfd; buffer = new unsigned char [size]; // no open needed, just read the pipe switch (read(pfd,buffer,size) { case -1: perror("read"); break; case 0: perror("EOF"); break; default: // call actual test procedure with // the parameter string converted // to the type of the test function test_func (*(TYPE*)&buffer); } delete buffer; }