Below are some examples of the Tracy API. If you are looking for the API reference, skip to the next chapter.
struct tracy *tracy;
tracy = tracy_init(options);
Tracy has several options that affect the tracing process. Options are passed to tracy_init in a bitwise manner.
The following tracy options are available:
TRACY_TRACE_CHILDREN
If this option is set, tracy will automatically trace all children of the process.
TRACY_VERBOSE
Tracy will be verbose and print information about events and internal logic.
TRACY_VERBOSE_SIGNAL
Tracy will print information relating to signals.
TRACY_VERBOSE_SYSCALL
Tracy will print information relating to system calls.
TRACY_MEMORY_FALLBACK
Enables a ptrace based (slow) memory fallback if the fast memory access method is not available.
TRACY_USE_SAFE_TRACE
Enables experimental tracing of all created children. Instead of relying on Linux’ mechanism to automatically trace all created children, we utilise our own safe tracing mechanism. Theoretically this should also work on BSD platforms, but has not yet been tested.
To start a process, use tracy_exec. Pass a NULL terminated string array as argv.
int main(int argc, char** argv) {
struct tracy *tracy;
tracy = tracy_init(TRACY_TRACE_CHILDREN | TRACY_VERBOSE |
TRACY_VERBOSE_SIGNAL | TRACY_VERBOSE_SYSCALL);
if (argc < 2) {
printf("Usage: ./example <program-name>\n");
return EXIT_FAILURE;
}
argv++; argc--;
/* Start child */
if (!tracy_exec(tracy, argc, argv)) {
perror("tracy_exec");
return EXIT_FAILURE;
}
/* Default event-loop */
tracy_main(tracy);
/* Free tracy */
tracy_free(tracy);
return 0;
}
Generally, you shouldn’t care about the specifics of tracy events and you can just use tracy_main instead. However, now follows a quick description of each event in Tracy’s event system.
TRACY_EVENT_SYSCALL
This event indicates that a tracee is trying to perform (or has just performed) a system call.
TRACY_EVENT_SIGNAL
This event indicates that a signal is to be delivered to a tracee.
TRACY_EVENT_INTERNAL
This indicates an internal event in Tracy. This event is used in asynchronous system calls and possibly other features in the future.
TRACY_EVENT_QUIT
This indicates that a tracee has been stopped or killed.
TRACY_EVENT_NONE
A none event is returned on error, or simply when there are no tracees left.
The tracy_main procedure is the default way to use Tracy events. The method does not return until all children have died. It honours the signal and system call hooks, but does not provide a lot of control over the event system. If you need more direct control, you could write your own version of tracy_main.
A very simple version:
int tracy_main(struct tracy * tracy) {
struct tracy_event * e;
main_loop_go_on = 1;
while (main_loop_go_on) {
e = tracy_wait_event(tracy, -1);
if (!e) {
fprintf(stderr, "tracy_main: tracy_wait_Event returned NULL\n");
continue;
}
if (e->type == TRACY_EVENT_NONE) {
break;
} else if (e->type == TRACY_EVENT_INTERNAL) {
} else if (e->type == TRACY_EVENT_SIGNAL) {
} else if (e->type == TRACY_EVENT_SYSCALL) {
} else if (e->type == TRACY_EVENT_QUIT) {
printf(_b("EVENT_QUIT from %d with signal %s (%ld)\n"),
e->child->pid, get_signal_name(e->signal_num),
e->signal_num);
if (e->child->pid == tracy->fpid) {
printf(_g("Our first child died.\n"));
}
tracy_remove_child(e->child);
continue;
}
if (!tracy_children_count(tracy)) {
break;
}
tracy_continue(e, 0);
}
return 0;
}
Tracy allows one hooking into any signal sent to a tracee as well as any system call executed by a tracee. The return values of the hooks (callbacks) determine the action that tracy will take.
See Signal hook and System call hooks for examples.
Tracy allows hooking into signals as well. One can hook into any signal to a tracee like this:
int hook_sig(struct tracy_event * e) {
if (e->signal_num == SIGTERM) {
return TRACY_HOOK_SUPPRESS;
}
return TRACY_HOOK_CONTINUE;
}
struct tracy * t = tracy_init(...);
tracy_set_signal_hook(t, hook_sig);
int hook_write(struct tracy_event * e) {
if (e->child->pre_syscall) {
printf("Pre-write system call\n");
} else {
printf("Pre-write system call\n");
}
return TRACY_HOOK_CONTINUE;
}
struct tracy * t = tracy_init(...);
tracy_set_hook(t, "write", TRACY_NATIVE_ABI, hook_write);
TRACY_HOOK_CONTINUE
Return this inside a hook when you want the execution to resume normally.
TRACY_HOOK_KILL_CHILD
Return this inside a hook if you want the child to be killed on hook return.
TRACY_HOOK_ABORT
Return this to completely kill tracy. Currently tracy will kill all the children and then generate a TRACY_EVENT_NONE.
Currently tracy kills its own process as well by calling exit().
TRACY_HOOK_SUPPRESS
Return this only from a signal hook. This will cause the signal that would normally be sent to be suppressed instead.
TRACY_HOOK_DETACH_CHILD
Return this if the child should be detached. Only valid for syscall hooks.
TRACY_HOOK_DENY
Return this only from a system call hook. This will cause the current system call to be denied.
The system call will be replaced by a getpid(2) and the return value will be set to -ENOSYS.
int hook_write(struct tracy_event * e) {
struct tracy_sc_args a;
if (e->child->pre_syscall) {
if (e->args.a0 == 2) {
memcpy(&a, &(e->args), sizeof(struct tracy_sc_args));
a.a0 = 1;
if (tracy_modify_syscall_args(e->child, a.syscall, &a)) {
return TRACY_HOOK_ABORT;
}
}
}
return TRACY_HOOK_CONTINUE;
}
int hook_write(struct tracy_event * e) {
if (e->child->pre_syscall) {
if(e->args.a0 == 1) {
printf("Denying write to stdout\n");
return TRACY_HOOK_DENY;
}
return TRACY_HOOK_CONTINUE;
}
int _write(struct tracy_event * e) {
long ret;
if (tracy_inject_syscall(e->child, get_syscall_number_abi("write", e->abi), &(e->args), &ret))
return TRACY_HOOK_ABORT;
printf("Returned: %ld\n", ret);
return TRACY_HOOK_CONTINUE;
}
int _write(struct tracy_event * e) {
if (e->child->inj.injected) {
printf("We just injected something. Result: %ld\n", e->args.return_code);
return 0;
}
if (tracy_inject_syscall_async(e->child, get_syscall_number_abi("write", e->abi), &(e->args), &_write))
return TRACY_HOOK_ABORT;
return TRACY_HOOK_CONTINUE;
}