Commit | Line | Data |
---|---|---|
aa61fd05 WN |
1 | /* |
2 | * Copyright (C) 2015, Wang Nan <wangnan0@huawei.com> | |
3 | * Copyright (C) 2015, Huawei Inc. | |
4 | */ | |
5 | ||
78478269 | 6 | #include <limits.h> |
aa61fd05 | 7 | #include <stdio.h> |
78478269 | 8 | #include <stdlib.h> |
aa61fd05 WN |
9 | #include "debug.h" |
10 | #include "llvm-utils.h" | |
aa61fd05 WN |
11 | |
12 | #define CLANG_BPF_CMD_DEFAULT_TEMPLATE \ | |
59f41af9 | 13 | "$CLANG_EXEC -D__KERNEL__ -D__NR_CPUS__=$NR_CPUS "\ |
4a4f66a1 | 14 | "-DLINUX_VERSION_CODE=$LINUX_VERSION_CODE " \ |
59f41af9 WN |
15 | "$CLANG_OPTIONS $KERNEL_INC_OPTIONS " \ |
16 | "-Wno-unused-value -Wno-pointer-sign " \ | |
17 | "-working-directory $WORKING_DIR " \ | |
18 | "-c \"$CLANG_SOURCE\" -target bpf -O2 -o -" | |
aa61fd05 WN |
19 | |
20 | struct llvm_param llvm_param = { | |
21 | .clang_path = "clang", | |
22 | .clang_bpf_cmd_template = CLANG_BPF_CMD_DEFAULT_TEMPLATE, | |
23 | .clang_opt = NULL, | |
24 | .kbuild_dir = NULL, | |
25 | .kbuild_opts = NULL, | |
9bc898c7 | 26 | .user_set_param = false, |
aa61fd05 WN |
27 | }; |
28 | ||
29 | int perf_llvm_config(const char *var, const char *value) | |
30 | { | |
31 | if (prefixcmp(var, "llvm.")) | |
32 | return 0; | |
33 | var += sizeof("llvm.") - 1; | |
34 | ||
35 | if (!strcmp(var, "clang-path")) | |
36 | llvm_param.clang_path = strdup(value); | |
37 | else if (!strcmp(var, "clang-bpf-cmd-template")) | |
38 | llvm_param.clang_bpf_cmd_template = strdup(value); | |
39 | else if (!strcmp(var, "clang-opt")) | |
40 | llvm_param.clang_opt = strdup(value); | |
41 | else if (!strcmp(var, "kbuild-dir")) | |
42 | llvm_param.kbuild_dir = strdup(value); | |
43 | else if (!strcmp(var, "kbuild-opts")) | |
44 | llvm_param.kbuild_opts = strdup(value); | |
f0784649 WN |
45 | else if (!strcmp(var, "dump-obj")) |
46 | llvm_param.dump_obj = !!perf_config_bool(var, value); | |
aa61fd05 WN |
47 | else |
48 | return -1; | |
9bc898c7 | 49 | llvm_param.user_set_param = true; |
aa61fd05 WN |
50 | return 0; |
51 | } | |
4cea3a9c WN |
52 | |
53 | static int | |
54 | search_program(const char *def, const char *name, | |
55 | char *output) | |
56 | { | |
57 | char *env, *path, *tmp = NULL; | |
58 | char buf[PATH_MAX]; | |
59 | int ret; | |
60 | ||
61 | output[0] = '\0'; | |
62 | if (def && def[0] != '\0') { | |
63 | if (def[0] == '/') { | |
64 | if (access(def, F_OK) == 0) { | |
65 | strlcpy(output, def, PATH_MAX); | |
66 | return 0; | |
67 | } | |
68 | } else if (def[0] != '\0') | |
69 | name = def; | |
70 | } | |
71 | ||
72 | env = getenv("PATH"); | |
73 | if (!env) | |
74 | return -1; | |
75 | env = strdup(env); | |
76 | if (!env) | |
77 | return -1; | |
78 | ||
79 | ret = -ENOENT; | |
80 | path = strtok_r(env, ":", &tmp); | |
81 | while (path) { | |
82 | scnprintf(buf, sizeof(buf), "%s/%s", path, name); | |
83 | if (access(buf, F_OK) == 0) { | |
84 | strlcpy(output, buf, PATH_MAX); | |
85 | ret = 0; | |
86 | break; | |
87 | } | |
88 | path = strtok_r(NULL, ":", &tmp); | |
89 | } | |
90 | ||
91 | free(env); | |
92 | return ret; | |
93 | } | |
94 | ||
95 | #define READ_SIZE 4096 | |
96 | static int | |
97 | read_from_pipe(const char *cmd, void **p_buf, size_t *p_read_sz) | |
98 | { | |
99 | int err = 0; | |
100 | void *buf = NULL; | |
101 | FILE *file = NULL; | |
102 | size_t read_sz = 0, buf_sz = 0; | |
76267147 | 103 | char serr[STRERR_BUFSIZE]; |
4cea3a9c WN |
104 | |
105 | file = popen(cmd, "r"); | |
106 | if (!file) { | |
107 | pr_err("ERROR: unable to popen cmd: %s\n", | |
76267147 | 108 | strerror_r(errno, serr, sizeof(serr))); |
4cea3a9c WN |
109 | return -EINVAL; |
110 | } | |
111 | ||
112 | while (!feof(file) && !ferror(file)) { | |
113 | /* | |
114 | * Make buf_sz always have obe byte extra space so we | |
115 | * can put '\0' there. | |
116 | */ | |
117 | if (buf_sz - read_sz < READ_SIZE + 1) { | |
118 | void *new_buf; | |
119 | ||
120 | buf_sz = read_sz + READ_SIZE + 1; | |
121 | new_buf = realloc(buf, buf_sz); | |
122 | ||
123 | if (!new_buf) { | |
124 | pr_err("ERROR: failed to realloc memory\n"); | |
125 | err = -ENOMEM; | |
126 | goto errout; | |
127 | } | |
128 | ||
129 | buf = new_buf; | |
130 | } | |
131 | read_sz += fread(buf + read_sz, 1, READ_SIZE, file); | |
132 | } | |
133 | ||
134 | if (buf_sz - read_sz < 1) { | |
135 | pr_err("ERROR: internal error\n"); | |
136 | err = -EINVAL; | |
137 | goto errout; | |
138 | } | |
139 | ||
140 | if (ferror(file)) { | |
141 | pr_err("ERROR: error occurred when reading from pipe: %s\n", | |
76267147 | 142 | strerror_r(errno, serr, sizeof(serr))); |
4cea3a9c WN |
143 | err = -EIO; |
144 | goto errout; | |
145 | } | |
146 | ||
147 | err = WEXITSTATUS(pclose(file)); | |
148 | file = NULL; | |
149 | if (err) { | |
150 | err = -EINVAL; | |
151 | goto errout; | |
152 | } | |
153 | ||
154 | /* | |
155 | * If buf is string, give it terminal '\0' to make our life | |
156 | * easier. If buf is not string, that '\0' is out of space | |
157 | * indicated by read_sz so caller won't even notice it. | |
158 | */ | |
159 | ((char *)buf)[read_sz] = '\0'; | |
160 | ||
161 | if (!p_buf) | |
162 | free(buf); | |
163 | else | |
164 | *p_buf = buf; | |
165 | ||
166 | if (p_read_sz) | |
167 | *p_read_sz = read_sz; | |
168 | return 0; | |
169 | ||
170 | errout: | |
171 | if (file) | |
172 | pclose(file); | |
173 | free(buf); | |
174 | if (p_buf) | |
175 | *p_buf = NULL; | |
176 | if (p_read_sz) | |
177 | *p_read_sz = 0; | |
178 | return err; | |
179 | } | |
180 | ||
181 | static inline void | |
182 | force_set_env(const char *var, const char *value) | |
183 | { | |
184 | if (value) { | |
185 | setenv(var, value, 1); | |
186 | pr_debug("set env: %s=%s\n", var, value); | |
187 | } else { | |
188 | unsetenv(var); | |
189 | pr_debug("unset env: %s\n", var); | |
190 | } | |
191 | } | |
192 | ||
193 | static void | |
194 | version_notice(void) | |
195 | { | |
196 | pr_err( | |
197 | " \tLLVM 3.7 or newer is required. Which can be found from http://llvm.org\n" | |
198 | " \tYou may want to try git trunk:\n" | |
199 | " \t\tgit clone http://llvm.org/git/llvm.git\n" | |
200 | " \t\t and\n" | |
201 | " \t\tgit clone http://llvm.org/git/clang.git\n\n" | |
202 | " \tOr fetch the latest clang/llvm 3.7 from pre-built llvm packages for\n" | |
203 | " \tdebian/ubuntu:\n" | |
204 | " \t\thttp://llvm.org/apt\n\n" | |
205 | " \tIf you are using old version of clang, change 'clang-bpf-cmd-template'\n" | |
206 | " \toption in [llvm] section of ~/.perfconfig to:\n\n" | |
207 | " \t \"$CLANG_EXEC $CLANG_OPTIONS $KERNEL_INC_OPTIONS \\\n" | |
208 | " \t -working-directory $WORKING_DIR -c $CLANG_SOURCE \\\n" | |
209 | " \t -emit-llvm -o - | /path/to/llc -march=bpf -filetype=obj -o -\"\n" | |
210 | " \t(Replace /path/to/llc with path to your llc)\n\n" | |
211 | ); | |
212 | } | |
213 | ||
d325d788 WN |
214 | static int detect_kbuild_dir(char **kbuild_dir) |
215 | { | |
216 | const char *test_dir = llvm_param.kbuild_dir; | |
217 | const char *prefix_dir = ""; | |
218 | const char *suffix_dir = ""; | |
219 | ||
220 | char *autoconf_path; | |
d325d788 WN |
221 | |
222 | int err; | |
223 | ||
224 | if (!test_dir) { | |
07bc5c69 WN |
225 | /* _UTSNAME_LENGTH is 65 */ |
226 | char release[128]; | |
227 | ||
228 | err = fetch_kernel_version(NULL, release, | |
229 | sizeof(release)); | |
230 | if (err) | |
d325d788 | 231 | return -EINVAL; |
d325d788 | 232 | |
07bc5c69 | 233 | test_dir = release; |
d325d788 WN |
234 | prefix_dir = "/lib/modules/"; |
235 | suffix_dir = "/build"; | |
236 | } | |
237 | ||
238 | err = asprintf(&autoconf_path, "%s%s%s/include/generated/autoconf.h", | |
239 | prefix_dir, test_dir, suffix_dir); | |
240 | if (err < 0) | |
241 | return -ENOMEM; | |
242 | ||
243 | if (access(autoconf_path, R_OK) == 0) { | |
244 | free(autoconf_path); | |
245 | ||
246 | err = asprintf(kbuild_dir, "%s%s%s", prefix_dir, test_dir, | |
247 | suffix_dir); | |
248 | if (err < 0) | |
249 | return -ENOMEM; | |
250 | return 0; | |
251 | } | |
252 | free(autoconf_path); | |
253 | return -ENOENT; | |
254 | } | |
255 | ||
0c6d18bf WN |
256 | static const char *kinc_fetch_script = |
257 | "#!/usr/bin/env sh\n" | |
258 | "if ! test -d \"$KBUILD_DIR\"\n" | |
259 | "then\n" | |
260 | " exit -1\n" | |
261 | "fi\n" | |
262 | "if ! test -f \"$KBUILD_DIR/include/generated/autoconf.h\"\n" | |
263 | "then\n" | |
264 | " exit -1\n" | |
265 | "fi\n" | |
266 | "TMPDIR=`mktemp -d`\n" | |
267 | "if test -z \"$TMPDIR\"\n" | |
268 | "then\n" | |
269 | " exit -1\n" | |
270 | "fi\n" | |
271 | "cat << EOF > $TMPDIR/Makefile\n" | |
272 | "obj-y := dummy.o\n" | |
273 | "\\$(obj)/%.o: \\$(src)/%.c\n" | |
274 | "\t@echo -n \"\\$(NOSTDINC_FLAGS) \\$(LINUXINCLUDE) \\$(EXTRA_CFLAGS)\"\n" | |
275 | "EOF\n" | |
276 | "touch $TMPDIR/dummy.c\n" | |
277 | "make -s -C $KBUILD_DIR M=$TMPDIR $KBUILD_OPTS dummy.o 2>/dev/null\n" | |
278 | "RET=$?\n" | |
279 | "rm -rf $TMPDIR\n" | |
280 | "exit $RET\n"; | |
281 | ||
d325d788 | 282 | static inline void |
0c6d18bf | 283 | get_kbuild_opts(char **kbuild_dir, char **kbuild_include_opts) |
d325d788 WN |
284 | { |
285 | int err; | |
286 | ||
0c6d18bf | 287 | if (!kbuild_dir || !kbuild_include_opts) |
d325d788 WN |
288 | return; |
289 | ||
290 | *kbuild_dir = NULL; | |
0c6d18bf | 291 | *kbuild_include_opts = NULL; |
d325d788 WN |
292 | |
293 | if (llvm_param.kbuild_dir && !llvm_param.kbuild_dir[0]) { | |
294 | pr_debug("[llvm.kbuild-dir] is set to \"\" deliberately.\n"); | |
295 | pr_debug("Skip kbuild options detection.\n"); | |
296 | return; | |
297 | } | |
298 | ||
299 | err = detect_kbuild_dir(kbuild_dir); | |
300 | if (err) { | |
301 | pr_warning( | |
302 | "WARNING:\tunable to get correct kernel building directory.\n" | |
303 | "Hint:\tSet correct kbuild directory using 'kbuild-dir' option in [llvm]\n" | |
304 | " \tsection of ~/.perfconfig or set it to \"\" to suppress kbuild\n" | |
305 | " \tdetection.\n\n"); | |
306 | return; | |
307 | } | |
0c6d18bf WN |
308 | |
309 | pr_debug("Kernel build dir is set to %s\n", *kbuild_dir); | |
310 | force_set_env("KBUILD_DIR", *kbuild_dir); | |
311 | force_set_env("KBUILD_OPTS", llvm_param.kbuild_opts); | |
312 | err = read_from_pipe(kinc_fetch_script, | |
313 | (void **)kbuild_include_opts, | |
314 | NULL); | |
315 | if (err) { | |
316 | pr_warning( | |
317 | "WARNING:\tunable to get kernel include directories from '%s'\n" | |
318 | "Hint:\tTry set clang include options using 'clang-bpf-cmd-template'\n" | |
319 | " \toption in [llvm] section of ~/.perfconfig and set 'kbuild-dir'\n" | |
320 | " \toption in [llvm] to \"\" to suppress this detection.\n\n", | |
321 | *kbuild_dir); | |
322 | ||
323 | free(*kbuild_dir); | |
324 | *kbuild_dir = NULL; | |
325 | return; | |
326 | } | |
327 | ||
328 | pr_debug("include option is set to %s\n", *kbuild_include_opts); | |
d325d788 WN |
329 | } |
330 | ||
f0784649 WN |
331 | static void |
332 | dump_obj(const char *path, void *obj_buf, size_t size) | |
333 | { | |
334 | char *obj_path = strdup(path); | |
335 | FILE *fp; | |
336 | char *p; | |
337 | ||
338 | if (!obj_path) { | |
339 | pr_warning("WARNING: No enough memory, skip object dumping\n"); | |
340 | return; | |
341 | } | |
342 | ||
343 | p = strrchr(obj_path, '.'); | |
344 | if (!p || (strcmp(p, ".c") != 0)) { | |
345 | pr_warning("WARNING: invalid llvm source path: '%s', skip object dumping\n", | |
346 | obj_path); | |
347 | goto out; | |
348 | } | |
349 | ||
350 | p[1] = 'o'; | |
351 | fp = fopen(obj_path, "wb"); | |
352 | if (!fp) { | |
353 | pr_warning("WARNING: failed to open '%s': %s, skip object dumping\n", | |
354 | obj_path, strerror(errno)); | |
355 | goto out; | |
356 | } | |
357 | ||
358 | pr_info("LLVM: dumping %s\n", obj_path); | |
359 | if (fwrite(obj_buf, size, 1, fp) != 1) | |
360 | pr_warning("WARNING: failed to write to file '%s': %s, skip object dumping\n", | |
361 | obj_path, strerror(errno)); | |
362 | fclose(fp); | |
363 | out: | |
364 | free(obj_path); | |
365 | } | |
366 | ||
4cea3a9c WN |
367 | int llvm__compile_bpf(const char *path, void **p_obj_buf, |
368 | size_t *p_obj_buf_sz) | |
369 | { | |
07bc5c69 WN |
370 | size_t obj_buf_sz; |
371 | void *obj_buf = NULL; | |
59f41af9 | 372 | int err, nr_cpus_avail; |
07bc5c69 | 373 | unsigned int kernel_version; |
4a4f66a1 | 374 | char linux_version_code_str[64]; |
4cea3a9c | 375 | const char *clang_opt = llvm_param.clang_opt; |
78478269 ACM |
376 | char clang_path[PATH_MAX], abspath[PATH_MAX], nr_cpus_avail_str[64]; |
377 | char serr[STRERR_BUFSIZE]; | |
0c6d18bf | 378 | char *kbuild_dir = NULL, *kbuild_include_opts = NULL; |
07bc5c69 | 379 | const char *template = llvm_param.clang_bpf_cmd_template; |
4cea3a9c | 380 | |
78478269 ACM |
381 | if (path[0] != '-' && realpath(path, abspath) == NULL) { |
382 | err = errno; | |
383 | pr_err("ERROR: problems with path %s: %s\n", | |
384 | path, strerror_r(err, serr, sizeof(serr))); | |
385 | return -err; | |
386 | } | |
387 | ||
4cea3a9c WN |
388 | if (!template) |
389 | template = CLANG_BPF_CMD_DEFAULT_TEMPLATE; | |
390 | ||
391 | err = search_program(llvm_param.clang_path, | |
392 | "clang", clang_path); | |
393 | if (err) { | |
394 | pr_err( | |
395 | "ERROR:\tunable to find clang.\n" | |
396 | "Hint:\tTry to install latest clang/llvm to support BPF. Check your $PATH\n" | |
397 | " \tand 'clang-path' option in [llvm] section of ~/.perfconfig.\n"); | |
398 | version_notice(); | |
399 | return -ENOENT; | |
400 | } | |
401 | ||
d325d788 WN |
402 | /* |
403 | * This is an optional work. Even it fail we can continue our | |
404 | * work. Needn't to check error return. | |
405 | */ | |
0c6d18bf | 406 | get_kbuild_opts(&kbuild_dir, &kbuild_include_opts); |
d325d788 | 407 | |
59f41af9 WN |
408 | nr_cpus_avail = sysconf(_SC_NPROCESSORS_CONF); |
409 | if (nr_cpus_avail <= 0) { | |
410 | pr_err( | |
411 | "WARNING:\tunable to get available CPUs in this system: %s\n" | |
76267147 | 412 | " \tUse 128 instead.\n", strerror_r(errno, serr, sizeof(serr))); |
59f41af9 WN |
413 | nr_cpus_avail = 128; |
414 | } | |
415 | snprintf(nr_cpus_avail_str, sizeof(nr_cpus_avail_str), "%d", | |
416 | nr_cpus_avail); | |
417 | ||
07bc5c69 WN |
418 | if (fetch_kernel_version(&kernel_version, NULL, 0)) |
419 | kernel_version = 0; | |
420 | ||
4a4f66a1 | 421 | snprintf(linux_version_code_str, sizeof(linux_version_code_str), |
07bc5c69 | 422 | "0x%x", kernel_version); |
4a4f66a1 | 423 | |
59f41af9 | 424 | force_set_env("NR_CPUS", nr_cpus_avail_str); |
4a4f66a1 | 425 | force_set_env("LINUX_VERSION_CODE", linux_version_code_str); |
4cea3a9c WN |
426 | force_set_env("CLANG_EXEC", clang_path); |
427 | force_set_env("CLANG_OPTIONS", clang_opt); | |
0c6d18bf | 428 | force_set_env("KERNEL_INC_OPTIONS", kbuild_include_opts); |
d325d788 | 429 | force_set_env("WORKING_DIR", kbuild_dir ? : "."); |
4cea3a9c WN |
430 | |
431 | /* | |
432 | * Since we may reset clang's working dir, path of source file | |
433 | * should be transferred into absolute path, except we want | |
434 | * stdin to be source file (testing). | |
435 | */ | |
436 | force_set_env("CLANG_SOURCE", | |
78478269 | 437 | (path[0] == '-') ? path : abspath); |
4cea3a9c WN |
438 | |
439 | pr_debug("llvm compiling command template: %s\n", template); | |
440 | err = read_from_pipe(template, &obj_buf, &obj_buf_sz); | |
441 | if (err) { | |
442 | pr_err("ERROR:\tunable to compile %s\n", path); | |
443 | pr_err("Hint:\tCheck error message shown above.\n"); | |
444 | pr_err("Hint:\tYou can also pre-compile it into .o using:\n"); | |
445 | pr_err(" \t\tclang -target bpf -O2 -c %s\n", path); | |
446 | pr_err(" \twith proper -I and -D options.\n"); | |
447 | goto errout; | |
448 | } | |
449 | ||
d325d788 | 450 | free(kbuild_dir); |
0c6d18bf | 451 | free(kbuild_include_opts); |
f0784649 WN |
452 | |
453 | if (llvm_param.dump_obj) | |
454 | dump_obj(path, obj_buf, obj_buf_sz); | |
455 | ||
4cea3a9c WN |
456 | if (!p_obj_buf) |
457 | free(obj_buf); | |
458 | else | |
459 | *p_obj_buf = obj_buf; | |
460 | ||
461 | if (p_obj_buf_sz) | |
462 | *p_obj_buf_sz = obj_buf_sz; | |
463 | return 0; | |
464 | errout: | |
d325d788 | 465 | free(kbuild_dir); |
0c6d18bf | 466 | free(kbuild_include_opts); |
4cea3a9c WN |
467 | free(obj_buf); |
468 | if (p_obj_buf) | |
469 | *p_obj_buf = NULL; | |
470 | if (p_obj_buf_sz) | |
471 | *p_obj_buf_sz = 0; | |
472 | return err; | |
473 | } | |
9bc898c7 WN |
474 | |
475 | int llvm__search_clang(void) | |
476 | { | |
477 | char clang_path[PATH_MAX]; | |
478 | ||
479 | return search_program(llvm_param.clang_path, "clang", clang_path); | |
480 | } |