Wednesday, 13 May 2015

ftrace code for userspace


/*
 * file : ne_ftrace_console_write.c
 * desc : a demo program that uses "ftrace" for viewing the kernel control 
 *        path taken when a write(2) is made to "/dev/tty1".
 *
 * notes: code based on snippets from 'Documentation/trace/ftrace.txt' 
 *
 * Siro Mugabi, Copyright (c) nairobi-embedded.org
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

#define prfmt(fmt) "%s:%d:: " fmt, __func__, __LINE__
#define prerr(fmt, ...) fprintf(stderr, prfmt(fmt), ##__VA_ARGS__);
static const char *find_debugfs(void)
{
    #undef  PATH
    #define PATH     256
    #define _STR(x) #x
    #define STR(x) _STR(x)

    static char debugfs[PATH + 1];
    static int debugfs_found;
    char type[100];
    FILE *fp;

    if (debugfs_found)
        return debugfs;

    if ((fp = fopen("/proc/mounts", "r")) == NULL)
        return NULL;

    while (fscanf(fp, "%*s %" STR(PATH) "s %99s %*s %*d %*d\n",
                    debugfs, type) == 2) {
        if (strcmp(type, "debugfs") == 0)
            break;
    }
    fclose(fp);

    if (strcmp(type, "debugfs") != 0)
        return NULL;

    debugfs_found = 1;
    return debugfs;
}

static int trace_write(int fd, const char *fmt, ...)
{
    va_list ap;
    char buf[256];
    char *pbuf = buf;
    int len, ret = -1;

    if (fd < 0 || !fmt)
        return ret;

    va_start(ap, fmt);
    len = vsnprintf(buf, 256, fmt, ap);
    va_end(ap);

    while (len != 0 && (ret = write(fd, pbuf, len)) != 0) {
        if (ret == -1) {
            if (errno == EINTR)
                continue;
            prerr("%s\n", strerror(errno));
            break;
        }
        len -= ret;
        pbuf += ret;
    }
    return ret;
}

int main(int arg, char **argv)
{
    const char *debugfs;
    char path[PATH + 1];
    int tty_fd, tr_on_fd, marker_fd, tracer_fd;

    debugfs = find_debugfs();
    if (!debugfs) {
        prerr("find_debugfs failed!\n");
        exit(EXIT_FAILURE);
    }

    /* eh-hem, this is permissible for demo code 
     * do not use "system(3)" in production code */
    system("sysctl kernel.ftrace_enabled=1");
    #ifdef TRACE_PID
    {
        pid_t pid = getpid();
        memset(path, 0, PATH + 1);
        snprintf(path, PATH + 1, "echo %d > %s/tracing/set_ftrace_pid", pid,
             debugfs);
        system(path);
    }
    #endif

    /* get "${debugfs}/tracing/current_tracer" file desc. */
    memset(path, 0, PATH + 1);
    strcpy(path, debugfs);
    strcat(path, "/tracing/current_tracer");
    if ((tracer_fd = open(path, O_WRONLY)) < 0) {
        prerr("%s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    /* get "${debugfs}/tracing/tracing_on" file desc. */
    memset(path, 0, PATH + 1);
    strcpy(path, debugfs);
    strcat(path, "/tracing/tracing_on");
    if ((tr_on_fd = open(path, O_WRONLY)) < 0) {
        prerr("%s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    /* get "${debugfs}/tracing/trace_marker" file desc. */
    memset(path, 0, PATH + 1);
    strcpy(path, debugfs);
    strcat(path, "/tracing/trace_marker");
    if ((marker_fd = open(path, O_WRONLY)) < 0) {
        prerr("%s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    /* get "/dev/tty1" file desc. */
    if ((tty_fd = open("/dev/tty1", O_WRONLY)) < 0) {
        prerr("%s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    /* clear any previous trace */
    if ((trace_write(tr_on_fd, "0") < 0) ||
            (trace_write(tracer_fd, "nop") < 0)) {
        prerr("trace_write failed!\n");
        exit(EXIT_FAILURE);
    }

    /* "echo function_graph > ${debugfs}/tracing/current_tracer" */
    if (trace_write(tracer_fd, "function_graph") < 0) {
        prerr("trace_write failed!\n");
        exit(EXIT_FAILURE);
    }

    /* "echo 1 > ${debugfs}/tracing/tracing_on" */
    if (trace_write(tr_on_fd, "1") < 0) {
        prerr("trace_write failed!\n");
        exit(EXIT_FAILURE);
    }

    /* finally, perform the trace */
    if (trace_write(marker_fd, "Before console write\n") < 0) {
        prerr("trace_write failed!\n");
        exit(EXIT_FAILURE);
    }

    if (trace_write(tty_fd, "Dunia, vipi?\n") < 0) {
        prerr("trace_write failed!\n");
        exit(EXIT_FAILURE);
    }

    if (trace_write(marker_fd, "After console write\n") < 0) {
        prerr("trace_write failed!\n");
        exit(EXIT_FAILURE);
    }

    /* "echo 0 > ${debugfs}/tracing/tracing_on" */
    if (trace_write(tr_on_fd, "0") < 0) {
        prerr("trace_write failed!\n");
        exit(EXIT_FAILURE);
    }

    /* "lazy" copying the trace output to pwd */
    {
        memset(path, 0, PATH + 1);
        snprintf(path, PATH + 1,
             "cat %s/tracing/trace > ftrace_output.txt", debugfs);
        system(path);
    }

    /* clear the ring buffer */
    if (trace_write(tracer_fd, "nop") < 0) {
        prerr("trace_write failed!\n");
        exit(EXIT_FAILURE);
    }

    #ifdef TRACE_PID
    /* "lazy" reset "set_ftrace_pid" */
    {
        memset(path, 0, PATH + 1);
        snprintf(path, PATH + 1, "echo > %s/tracing/set_ftrace_pid",
             debugfs);
        system(path);
    }
    #endif

    close(tty_fd);
    close(tr_on_fd);
    close(marker_fd);
    close(tracer_fd);
    exit(EXIT_SUCCESS);
}

No comments:

Post a Comment