API Example

Below are some examples of the Tracy API. If you are looking for the API reference, skip to the next chapter.

Creating a Tracy instance

struct tracy *tracy;

tracy = tracy_init(options);

Tracy 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.

Tracing a process

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;

}

Handling Tracy events

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.

tracy_main

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.

Your own event loop

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 hooks

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.

Signal hook

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);

System call hooks

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);

Hook return values

  • 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.

System call modification

Changing the arguments

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;
}

Denying a system call

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;
}

System call injection

Synchronous injection

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;
}

Asynchronous injection

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;
}

Cleaning up