aboutsummaryrefslogtreecommitdiffstats
path: root/tools/fops_probe/fops_probe.cc
blob: c3c3332a6ddab29f98058529e89d532c31b5cd7a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// Copyright 2019 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

// fops_probe utility helps to understand what file_operations callbacks
// are attached to a particular file. Requries KCOV and KALLSYMS.
// Build with:
//	g++ tools/fops_probe/fops_probe.cc -Wall -static -o fops_probe
// Then copy the binary to target machine and run as:
//	./fops_probe /dev/fb0
// You should see output similar to:
//
//	ffffffff81bcccb9 vfs_read
//	................
//	ffffffff83af85c3 fb_read
//	ffffffff83b52af5 cirrusfb_sync
//
//	ffffffff81bcd219 vfs_write
//	................
//	ffffffff83af7fe2 fb_write
//	ffffffff83b52af5 cirrusfb_sync
//
//	ffffffff81c1b745 do_vfs_ioctl
//	ffffffff83af7ea9 fb_ioctl
//
//	ffffffff81a4ea44 do_mmap
//	................
//	ffffffff83af716c fb_mmap
//
// which allows to understand what callbacks are associated with /dev/fb0.

#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <linux/kcov.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <functional>
#include <map>
#include <set>
#include <string>

#define COVER_SIZE (1 << 20)

typedef std::map<long long, std::string> kallsyms_map_t;

static __attribute__((noreturn)) __attribute__((format(printf, 1, 2))) void failf(const char* msg, ...);
static kallsyms_map_t read_kallsyms();
static bool is_blacklisted(const std::string& sym);
static void probe_callback(uint64_t* cover, const kallsyms_map_t& kallsyms,
			   const std::string& start_sym, std::function<void(void)> fn);

int main(int argc, char** argv)
{
	if (argc != 2)
		failf("usage: fops_probe file");
	int fd = open(argv[1], O_RDWR);
	if (fd == -1) {
		fd = open(argv[1], O_RDONLY);
		if (fd == -1)
			failf("failed to open %s", argv[1]);
	}
	const kallsyms_map_t kallsyms = read_kallsyms();
	int kcov = open("/sys/kernel/debug/kcov", O_RDWR);
	if (kcov == -1)
		failf("failed to open /sys/kernel/debug/kcov");
	if (ioctl(kcov, KCOV_INIT_TRACE, COVER_SIZE))
		failf("KCOV_INIT_TRACE failed");
	uint64_t* cover = (uint64_t*)mmap(NULL, COVER_SIZE * 8, PROT_READ | PROT_WRITE, MAP_SHARED, kcov, 0);
	if (cover == MAP_FAILED)
		failf("cover mmap failed");
	if (ioctl(kcov, KCOV_ENABLE, KCOV_TRACE_PC))
		failf("KCOV_ENABLE failed");
	probe_callback(cover, kallsyms, "do_vfs_ioctl", [&]() { ioctl(fd, 0, 0); });
	probe_callback(cover, kallsyms, "do_mmap", [&]() { mmap(0, 4096, PROT_READ, MAP_PRIVATE, fd, 0); });
	probe_callback(cover, kallsyms, "vfs_write", [&]() { write(fd, 0, 0); });
	probe_callback(cover, kallsyms, "vfs_read", [&]() { read(fd, 0, 0); });
	return 0;
}

void probe_callback(uint64_t* cover, const kallsyms_map_t& kallsyms,
		    const std::string& start_sym, std::function<void(void)> fn)
{
	__atomic_store_n(&cover[0], 0, __ATOMIC_SEQ_CST);
	fn();
	uint64_t ncover = __atomic_load_n(&cover[0], __ATOMIC_SEQ_CST);
	bool started = false;
	std::set<std::string> seen;
	for (uint64_t i = 0; i < ncover; i++) {
		long long pc = cover[i + 1];
		auto it = kallsyms.lower_bound(pc - 1);
		const std::string& sym = it == kallsyms.begin() ? "" : (--it)->second;
		if (!started && sym != start_sym)
			continue;
		started = true;
		if (!seen.insert(sym).second || is_blacklisted(sym))
			continue;
		printf("%0llx %s\n", pc, sym.c_str());
	}
	printf("\n");
}

bool is_blacklisted(const std::string& sym)
{
	static const char* blacklist[] = {
	    "security",
	    "tomoyo",
	    "selinux",
	    "apparmor",
	    "smack",
	    "policy",
	    "stack_trace",
	    "should_fail",
	    "debug",
	    "trace",
	    "snprintf",
	    "vsnprintf",
	};
	for (size_t i = 0; i < sizeof(blacklist) / sizeof(blacklist[0]); i++) {
		if (!strncmp(sym.c_str(), blacklist[i], strlen(blacklist[i])))
			return true;
	}
	return false;
}

kallsyms_map_t read_kallsyms()
{
	kallsyms_map_t kallsyms;
	FILE* f = fopen("/proc/kallsyms", "r");
	if (f == NULL)
		failf("failed to open /proc/kallsyms");
	size_t n = 0;
	char* line = NULL;
	for (;;) {
		ssize_t len = getline(&line, &n, f);
		if (len < 0)
			break;
		long long pc;
		char typ;
		char sym[1024];
		if (sscanf(line, "%016llx %c %s\n", &pc, &typ, sym) != 3)
			failf("bad line in kallsyms: %s", line);
		if (typ != 't' && typ != 'T')
			continue;
		kallsyms[pc] = sym;
	}
	free(line);
	fclose(f);
	return kallsyms;
}

void failf(const char* msg, ...)
{
	int e = errno;
	va_list args;
	va_start(args, msg);
	vfprintf(stderr, msg, args);
	va_end(args);
	fprintf(stderr, " (errno: %s)\n", strerror(e));
	exit(1);
}